summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSadaf Ebrahimi <sadafebrahimi@google.com>2023-11-15 21:17:54 +0000
committerSadaf Ebrahimi <sadafebrahimi@google.com>2023-11-15 21:17:54 +0000
commit5af14ed1c587647836230c82b57bc9b95bc0bd6f (patch)
tree864c52fff91ada9d6a095df9ed49786356575bf6
parent825d41a31bac6c4685a22330286cc9ed4042fe39 (diff)
downloadukey2-5af14ed1c587647836230c82b57bc9b95bc0bd6f.tar.gz
Use external/rust/beto-rust/nearby/connections/ukey2 or go through the go/android-3p process if you need this in future. Test: TreeHugger Change-Id: Ie410b7ca9eabd6cc500d38e9ba9d6215df117f74
-rw-r--r--.gitignore3
-rw-r--r--Android.bp48
-rw-r--r--CMakeLists.txt50
-rw-r--r--CONTRIBUTING.md28
-rw-r--r--LICENSE202
-rw-r--r--METADATA13
-rw-r--r--MODULE_LICENSE_APACHE20
-rw-r--r--OWNERS1
-rw-r--r--README0
-rw-r--r--README.md392
-rw-r--r--build.gradle64
-rw-r--r--cmake/local_build_protobuf.cmake42
-rw-r--r--cmake/local_build_setup.cmake26
-rw-r--r--cmake/proto_defs.cmake42
-rw-r--r--src/main/CMakeLists.txt18
-rw-r--r--src/main/cpp/CMakeLists.txt17
-rw-r--r--src/main/cpp/include/securegcm/d2d_connection_context_v1.h89
-rw-r--r--src/main/cpp/include/securegcm/d2d_crypto_ops.h78
-rw-r--r--src/main/cpp/include/securegcm/java_util.h57
-rw-r--r--src/main/cpp/include/securegcm/ukey2_handshake.h263
-rw-r--r--src/main/cpp/src/securegcm/CMakeLists.txt47
-rw-r--r--src/main/cpp/src/securegcm/d2d_connection_context_v1.cc228
-rw-r--r--src/main/cpp/src/securegcm/d2d_crypto_ops.cc151
-rw-r--r--src/main/cpp/src/securegcm/java_util.cc60
-rw-r--r--src/main/cpp/src/securegcm/ukey2_handshake.cc715
-rw-r--r--src/main/cpp/src/securegcm/ukey2_shell.cc297
-rw-r--r--src/main/cpp/test/securegcm/CMakeLists.txt31
-rw-r--r--src/main/cpp/test/securegcm/d2d_connection_context_v1_test.cc124
-rw-r--r--src/main/cpp/test/securegcm/d2d_crypto_ops_test.cc158
-rw-r--r--src/main/cpp/test/securegcm/java_util_test.cc84
-rw-r--r--src/main/java/com/google/security/annotations/CryptoAnnotation.java68
-rw-r--r--src/main/java/com/google/security/annotations/SuppressInsecureCipherModeCheckerNoReview.java57
-rw-r--r--src/main/java/com/google/security/annotations/SuppressInsecureCipherModeCheckerPendingReview.java59
-rw-r--r--src/main/java/com/google/security/annotations/SuppressInsecureCipherModeCheckerReviewed.java58
-rw-r--r--src/main/java/com/google/security/cryptauth/lib/securegcm/D2DConnectionContext.java274
-rw-r--r--src/main/java/com/google/security/cryptauth/lib/securegcm/D2DConnectionContextV0.java118
-rw-r--r--src/main/java/com/google/security/cryptauth/lib/securegcm/D2DConnectionContextV1.java146
-rw-r--r--src/main/java/com/google/security/cryptauth/lib/securegcm/D2DCryptoOps.java239
-rw-r--r--src/main/java/com/google/security/cryptauth/lib/securegcm/D2DDiffieHellmanKeyExchangeHandshake.java307
-rw-r--r--src/main/java/com/google/security/cryptauth/lib/securegcm/D2DHandshakeContext.java158
-rw-r--r--src/main/java/com/google/security/cryptauth/lib/securegcm/Ed25519.java270
-rw-r--r--src/main/java/com/google/security/cryptauth/lib/securegcm/EnrollmentCryptoOps.java233
-rw-r--r--src/main/java/com/google/security/cryptauth/lib/securegcm/HandshakeException.java32
-rw-r--r--src/main/java/com/google/security/cryptauth/lib/securegcm/KeyEncoding.java180
-rw-r--r--src/main/java/com/google/security/cryptauth/lib/securegcm/SecureGcmConstants.java49
-rw-r--r--src/main/java/com/google/security/cryptauth/lib/securegcm/TransportCryptoOps.java268
-rw-r--r--src/main/java/com/google/security/cryptauth/lib/securegcm/Ukey2Handshake.java1041
-rw-r--r--src/main/java/com/google/security/cryptauth/lib/securegcm/device_to_device_messages_config.asciipb3
-rw-r--r--src/main/java/com/google/security/cryptauth/lib/securegcm/securegcm_config.asciipb4
-rw-r--r--src/main/java/com/google/security/cryptauth/lib/securemessage/CryptoOps.java564
-rw-r--r--src/main/java/com/google/security/cryptauth/lib/securemessage/PublicKeyProtoUtil.java675
-rw-r--r--src/main/java/com/google/security/cryptauth/lib/securemessage/SecureMessageBuilder.java277
-rw-r--r--src/main/java/com/google/security/cryptauth/lib/securemessage/SecureMessageParser.java270
-rw-r--r--src/main/javatest/com/google/security/cryptauth/lib/securegcm/D2DConnectionContextTest.java568
-rw-r--r--src/main/javatest/com/google/security/cryptauth/lib/securegcm/D2DDiffieHellmanKeyExchangeHandshakeTest.java432
-rw-r--r--src/main/javatest/com/google/security/cryptauth/lib/securegcm/Ed25519Test.java195
-rw-r--r--src/main/javatest/com/google/security/cryptauth/lib/securegcm/EnrollmentCryptoOpsTest.java134
-rw-r--r--src/main/javatest/com/google/security/cryptauth/lib/securegcm/KeyEncodingTest.java189
-rw-r--r--src/main/javatest/com/google/security/cryptauth/lib/securegcm/TransportCryptoOpsTest.java110
-rw-r--r--src/main/javatest/com/google/security/cryptauth/lib/securegcm/Ukey2CppCompatibilityTest.java124
-rw-r--r--src/main/javatest/com/google/security/cryptauth/lib/securegcm/Ukey2HandshakeTest.java818
-rw-r--r--src/main/javatest/com/google/security/cryptauth/lib/securegcm/Ukey2ShellCppWrapper.java342
-rw-r--r--src/main/javatest/com/google/security/cryptauth/lib/securemessage/CryptoOpsTest.java172
-rw-r--r--src/main/javatest/com/google/security/cryptauth/lib/securemessage/NullsGoogle3Test.java42
-rw-r--r--src/main/javatest/com/google/security/cryptauth/lib/securemessage/PublicKeyProtoUtilTest.java412
-rw-r--r--src/main/javatest/com/google/security/cryptauth/lib/securemessage/SecureMessageSimpleTestVectorTest.java403
-rw-r--r--src/main/javatest/com/google/security/cryptauth/lib/securemessage/SecureMessageTest.java766
-rw-r--r--src/main/proto/CMakeLists.txt32
-rw-r--r--src/main/proto/device_to_device_messages.proto81
-rw-r--r--src/main/proto/passwordless_auth_payloads.proto37
-rw-r--r--src/main/proto/proximity_payloads.proto59
-rw-r--r--src/main/proto/securegcm.proto308
-rw-r--r--src/main/proto/securemessage.proto126
-rw-r--r--src/main/proto/ukey.proto105
74 files changed, 1 insertions, 14132 deletions
diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index f53951a..0000000
--- a/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-build/**
-.gradle/**
-.idea/**
diff --git a/Android.bp b/Android.bp
deleted file mode 100644
index 1376405..0000000
--- a/Android.bp
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright 2019 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
-//
-// https://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.
-
-// Android build file for building the library in AOSP
-// https://android.googlesource.com/platform/external/ukey2
-
-package {
- default_applicable_licenses: ["external_ukey2_license"],
-}
-
-// Added automatically by a large-scale-change
-// http://go/android-license-faq
-license {
- name: "external_ukey2_license",
- visibility: [":__subpackages__"],
- license_kinds: [
- "SPDX-license-identifier-Apache-2.0",
- ],
- license_text: [
- "LICENSE",
- ],
-}
-
-java_library {
- name: "ukey2",
- proto: {
- type: "lite",
- include_dirs: ["external/ukey2/src/main/proto"],
- },
- srcs: [
- "**/*.proto",
- "src/main/java/**/*.java",
- ],
- libs: [
- "guava",
- ],
-}
diff --git a/CMakeLists.txt b/CMakeLists.txt
deleted file mode 100644
index 02ddda2..0000000
--- a/CMakeLists.txt
+++ /dev/null
@@ -1,50 +0,0 @@
-# Copyright 2020 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
-#
-# https://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.
-
-cmake_minimum_required(VERSION 3.0.2)
-
-project(ukey2)
-
-option(ukey2_USE_LOCAL_PROTOBUF
- "Use local copy of protobuf library and compiler" OFF)
-
-option(ukey2_USE_LOCAL_ABSL
- "Use local copy of abseil-cpp library" OFF)
-
-include(cmake/proto_defs.cmake)
-include(cmake/local_build_setup.cmake)
-
-if (ukey2_USE_LOCAL_PROTOBUF)
- include(cmake/local_build_protobuf.cmake)
-endif()
-
-if (ukey2_USE_LOCAL_ABSL)
- if (NOT TARGET absl::base)
- add_subdirectory(third_party/absl)
- endif()
-else()
- find_package(absl REQUIRED)
-endif()
-
-find_package(Protobuf REQUIRED)
-
-enable_testing()
-
-add_subdirectory(src/main)
-if (NOT TARGET securemessage)
-add_subdirectory(third_party/secure_message)
-endif()
-if (NOT TARGET gtest)
-add_subdirectory(third_party/gtest)
-endif()
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
deleted file mode 100644
index 939e534..0000000
--- a/CONTRIBUTING.md
+++ /dev/null
@@ -1,28 +0,0 @@
-# How to Contribute
-
-We'd love to accept your patches and contributions to this project. There are
-just a few small guidelines you need to follow.
-
-## Contributor License Agreement
-
-Contributions to this project must be accompanied by a Contributor License
-Agreement. You (or your employer) retain the copyright to your contribution;
-this simply gives us permission to use and redistribute your contributions as
-part of the project. Head over to <https://cla.developers.google.com/> to see
-your current agreements on file or to sign a new one.
-
-You generally only need to submit a CLA once, so if you've already submitted one
-(even if it was for a different project), you probably don't need to do it
-again.
-
-## Code reviews
-
-All submissions, including submissions by project members, require review. We
-use GitHub pull requests for this purpose. Consult
-[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
-information on using pull requests.
-
-## Community Guidelines
-
-This project follows [Google's Open Source Community
-Guidelines](https://opensource.google.com/conduct/).
diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index d645695..0000000
--- a/LICENSE
+++ /dev/null
@@ -1,202 +0,0 @@
-
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
- 1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
-
- APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "[]"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
- Copyright [yyyy] [name of copyright owner]
-
- 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.
diff --git a/METADATA b/METADATA
deleted file mode 100644
index d08b2ea..0000000
--- a/METADATA
+++ /dev/null
@@ -1,13 +0,0 @@
-name: "Ukey2"
-description:
- "UKEY2 is a Diffie-Hellman based authenticated key exchange protocol."
-
-third_party {
- url {
- type: ARCHIVE
- value: "https://github.com/google/ukey2.git"
- }
- version: "0275885d8e6038c39b8a8ca55e75d1d4d1727f47"
- license_type: NOTICE
- last_upgrade_date { year: 2021 month: 2 day: 8 }
-}
diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2
deleted file mode 100644
index e69de29..0000000
--- a/MODULE_LICENSE_APACHE2
+++ /dev/null
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..7529cb9
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1 @@
+include platform/system/core:/janitors/OWNERS
diff --git a/README b/README
deleted file mode 100644
index e69de29..0000000
--- a/README
+++ /dev/null
diff --git a/README.md b/README.md
deleted file mode 100644
index 87a652d..0000000
--- a/README.md
+++ /dev/null
@@ -1,392 +0,0 @@
-# Ukey2
-This is not an officially supported Google product
-
-**Coathored by:** Alexei Czeskis, Thai Duong, Eduardo' Vela'' \<Nava\>, and Adam Stubblefield.
-
-**Status:**
-Implemented in Java by Alexei Czeskis (aczeskis@google.com)
-Ported from Java to C++ by Tim Song (tengs@google.com)
-
-**Design reviewers:** Thai Duong, Bruno Blanchet, Martin Abadi, and Bo Wang
-
-**Implementation reviewer**: Thai Duong
-
-**Last Updated:** roughly in September 2016
-
-
-
-# Overview
-
-UKEY2 is a Diffie-Hellman based authenticated key exchange protocol.
-
-At the end of a UKEY2 run, a client and server have a shared master secret that can be used to
-derive keys which can be used in a subsequent protocol. UKEY2 only implicitly guarantees that
-servers know that clients believe the protocol finished correctly; that is, until a server
-receives a message on the next protocol from the client it does not know that the handshake
-completed.
-
-The intended usage of UKEY2 is to establish a secure channel between two user devices,
-e.g., laptop with Chromecast, phone with Google Glass, etc. The secure channel then can be used to
-transmit passwords or other credentials. It is especially useful when one wants to connect a brand
- new device to a password-protected WIFI network. UKEY2 is also usable over low-bandwidth
-transports like Bluetooth Low Energy (see [Performance](#performance)).
-
-# Message Framing
-
-Each UKEY2 message is framed inside an outer protobuf message:
-
-
-```
-message Ukey2Message {
- enum Type {
- UNKNOWN_DO_NOT_USE = 0;
- ALERT = 1;
- CLIENT_INIT = 2;
- SERVER_INIT = 3;
- CLIENT_FINISH = 4;
- }
-
- optional Type message_type = 1; // Identifies message type
- optional bytes message_data = 2; // Actual message, to be parsed according to
- // message_type
-}
-```
-
-
-
-# Alerts
-
-In case an error occurs, the client and server will reply with an Alert:
-
-
-```
-message Ukey2Alert {
- enum AlertType {
- // Framing errors
- BAD_MESSAGE = 1; // The message could not be deserialized
- BAD_MESSAGE_TYPE = 2; // message_type has an undefined value
- INCORRECT_MESSAGE = 3; // message_type received does not correspond to expected
- // type at this stage of the protocol
- BAD_MESSAGE_DATA = 4; // Could not deserialize message_data as per value in
- // message_type
-
- // ClientInit and ServerInit errors
- BAD_VERSION = 100; // version is invalid; server cannot find suitable version
- // to speak with client.
- BAD_RANDOM = 101; // Random data is missing or of incorrect length
- BAD_HANDSHAKE_CIPHER = 102; // No suitable handshake ciphers were found
- BAD_NEXT_PROTOCOL = 103; // The next protocol is missing, unknown, or unsupported
- BAD_PUBLIC_KEY = 104; // The public key could not be parsed
-
- // Other errors
- INTERNAL_ERROR = 200; // An internal error has occurred. error_message may
- // contain additional details for logging and debugging.
- }
-
- optional AlertType type = 1;
- optional string error_message = 2;
-}
-```
-
-
-The type corresponds to the error that caused the `Alert` to be sent. Upon encountering an error,
-clients and servers send an Alert of the proper type and close the connection; all alerts are
-fatal. Upon receiving an `Alert`, clients and servers must close the connection, even if they
-cannot parse the `Alert`. The `Alert` message may contain an optional `error_message` string
-that may be used to describe error details for logging.
-
-# Handshake Ciphersuites
-
-UKEY2 supports negotiation of the cryptographic primitives used in the handshake. Two primitives
-are required, a Diffie-Hellman function and a cryptographic hash function, which are represented
-by a single enum:
-
-
-```
-enum Ukey2HandshakeCipher {
- RESERVED = 0;
- P256_SHA512 = 100; // NIST P-256 used for ECDH, SHA512 used for commitment
- CURVE25519_SHA512 = 200; // Curve 25519 used for ECDH, SHA512 used for commitment
-}
-```
-
-
-The implementations of all primitives must resist timing side-channel attacks. A summary of
-handshake ciphersuite negotiation is (see ClientInit and ServerInit messages for full details):
-
-* The client enumerates the primitives it supports and the server choose the highest (by enum value) cipher that it also supports.
-* The server replies with a public key using the chosen cipher and sends its own list of supported handshake cipher suites so that the client can verify that the right selection was made.
-
-
-# Handshake Details
-
-The UKEY2 handshake consists of three messages. First, the client sends a `ClientInit` message to
-the server -- conceptually, this consists of a list of cipher suites and a commitment to an
-ephemeral public key for each suite. The server responds with a `ServerInit` -- conceptually,
-this is the server's chosen cipher suite and an ephemeral public key for the cipher suites
-selected by the server. Finally, the client responds with a `ClientFinished` -- conceptually,
-this consists of an ephemeral public key matching the cipher suite selected by the server.
-
-After the handshake, both client and server derive authentication strings, which may be shown to
-users for visual comparison or sent over some other channel in order to authenticate the handshake.
-The client and server also derive session keys for the next protocol.
-
-## The `ClientInit` Message
-
-The `ClientInit` message is defined as follows:
-
-
-```
-message Ukey2ClientInit {
- optional int32 version = 1; // highest supported version for rollback protection
- optional bytes random = 2; // random bytes for replay/reuse protection
-
- // One commitment (hash of ClientFinished containing public key) per supported cipher
- message CipherCommitment {
- optional Ukey2HandshakeCipher handshake_cipher = 1;
- optional bytes commitment = 2;
- }
- repeated CipherCommitment cipher_commitments = 3;
-
- // Next protocol that the client wants to speak.
- optional string next_protocol = 4;
-}
-```
-
-
-The `version` field is the maximum version that the client supports. It should be 1 for now. The `random` field is exactly 32 cryptographically secure random bytes. The `cipher_commitment` field is a protobuf consisting of a handshake cipher and a commitment which is a hash of the `ClientFinished` message that would be sent if the cipher were selected (the serialized, including framing, raw bytes of the last handshake message sent by the client), calculated with the hash function and the Diffie-Hellman function from the handshake cipher. The client includes each commitment in the order of their preference. Note that only one commitment per `handshake_cipher` is allowed. The client also includes the `next_protocol` field that specifies that the client wants to use to speak to the server. Note that this protocol must implicitly imply a key length. UKEY2, however, does not provide a namespace for the `next_protocol` values in order to provide layers separation between the handshake and the next protocols.
-
-
-## Interpreting `ClientInit`
-
-Upon receiving the `ClientInit` message, the server should:
-
-
-
-1. Deserialize the protobuf; send an `Alert.BAD_MESSAGE` message if deserialization fails.
-1. Verify that `message_type == Type.CLIENT_INIT`; send an `Alert.BAD_MESSAGE_TYPE` message if mismatch occurs.
-1. Deserialize `message_data` as a `ClientInit` message; send an `Alert.BAD_MESSAGE_DATA` message if deserialization fails.
-1. Check that `version == 1`; send `Alert.BAD_VERSION` message if mismatch.
-1. Check that `random` is exactly 32 bytes; send `Alert.BAD_RANDOM` message if not.
-1. Check to see if any of the `handshake_cipher` in `cipher_commitment` are acceptable. Servers should select the first `handshake_cipher` that it finds acceptable to support clients signaling deprecated but supported HandshakeCiphers. If no `handshake_cipher` is acceptable (or there are no HandshakeCiphers in the message), the server sends an `Alert.BAD_HANDSHAKE_CIPHER` message.
-1. Checks that `next_protocol` contains a protocol that the server supports. Send an `Alert.BAD_NEXT_PROTOCOL` message if not.
-
-If no alerts have been sent, the server replies with the `ServerInit` message.
-
-
-## The `ServerInit` Message
-
-The `ServerInit` message is as follows
-
-
-```
-message Ukey2ServerInit {
- optional int32 version = 1; // highest supported version for rollback protection
- optional bytes random = 2; // random bytes for replay/reuse protection
-
- // Selected Cipher and corresponding public key
- optional Ukey2HandshakeCipher handshake_cipher = 3;
- optional bytes public_key = 4;
-}
-```
-
-
-For now, `version` must be 1. The random field is exactly 32 cryptographically secure random
-bytes. The `handshake_cipher` field contains the server-chosen `HandshakeCipher`. The
-`public_key` field contains the server-chosen corresponding public key.
-
-
-## Interpreting `ServerInit`
-
-When a client receives a `ServerInit` after having sent a `ClientInit`, it performs the following actions:
-
-
-1. Deserialize the protobuf; send an `Alert.BAD_MESSAGE` message if deserialization fails.
-1. Verify that `message_type == Type.SERVER_INIT`; send an `Alert.BAD_MESSAGE_TYPE` message if mismatch occurs.
-1. Deserialize `message_data` as a `ServerInit` message; send an `Alert.BAD_MESSAGE_DATA` message if deserialization fails.
-1. Check that `version == 1`; send `Alert.BAD_VERSION` message if mismatch.
-1. Check that `random` is exactly 32 bytes; send `Alert.BAD_RANDOM` message if not.
-1. Check that `handshake_cipher` matches a handshake cipher that was sent in
-`ClientInit.cipher_commitments`. If not, send an `Alert.BAD_HANDSHAKECIPHER` message.
-1. Check that `public_key` parses into a correct public key structure. If not, send an `Alert.BAD_PUBLIC_KEY` message.
-
-If no alerts have been sent, the client replies with the `ClientFinished` message. After sending
-the `ClientFinished` message, the Client considers the handshake complete.
-
-
-**IMPORTANT:** The client should compute the authentication string `AUTH_STRING` and
-the next-protocol secret `NEXT_SECRET` (see below). The client should use an out-of-band
-channel to verify the authentication string before proceeding to the next protocol.
-
-
-## The ClientFinished Message
-
-The `ClientFinished` message is as follows:
-
-
-```
-message Ukey2ClientFinished {
- optional bytes public_key = 1; // public key matching selected handshake cipher
-}
-```
-
-
-The `public_key` contains the Client's public key (whose commitment was sent in the `ClientInit`
-message) for the server-selected handshake cipher.
-
-
-## Interpreting ClientFinished
-
-When a server receives a `ClientFinished` after having sent a `ServerInit`, it performs the
-following actions:
-
-
-1. Deserialize the protobuf; terminate the connection if deserialization fails.
-1. Verify that `message_type == Type.CLIENT_FINISHED`; terminate the connection if mismatch occurs.
-1. Verify that the hash of the `ClientFinished` matches the expected commitment for the chosen `handshake_cipher` from `ClientInit`. Terminate the connection if the expected match fails.
-1. Deserialize `message_data` as a `ClientFinished` message; terminate the connection if deserialization fails.
-1. Check that `public_key` parses into a correct public key structure. If not, terminate the connection.
-
-Note that because the client is not expecting a response, any error results in connection termination.
-
-After parsing the `ClientFinished` message, the Server considers the handshake complete.
-
-
-**IMPORTANT:** The server should compute the authentication string `AUTH_STRING` and the
-next-protocol secret `NEXT_SECRET` (see below). The server should use an out-of-band channel to
-verify the authentication string before proceeding to the next protocol.
-
-
-# Deriving the Authentication String and the Next-Protocol Secret
-
-Let `DHS` = the negotiated Diffie-Hellman key derived from the Client and Server public keys.
-
-Let `M_1` = the serialized (including framing) raw bytes of the first message sent by
-the client
-
-Let `M_2` = the serialized (including framing) raw bytes of the first message sent by
-the server
-
-Let `Hash` = the hash from HandshakeCipher
-
-Let `L_auth` = length of authentication string in bytes. Note that this length can
-be short (e.g., a 6 digit visual confirmation code).
-
-Let `L_next` = length of next protocol key
-
-Let `HKDF-Extract` and `HKDF-Expand` be as defined in [RFC5869](https://tools.ietf.org/html/rfc5869)
-instantiated with the hash from the `HandshakeCipher`.
-
-Let `PRK_AUTH = HKDF-Extract("UKEY2 v1 auth", DHS)`
-
-Let `PRK_NEXT = HKDF-Extract("UKEY2 v1 next", DHS)`
-
-Then `AUTH_STRING = HKDF-Expand(PRK_AUTH, M_1|M_2, L_auth)`
-
-Then `NEXT_SECRET = HKDF-Expand(PRK_NEXT, M_1|M_2, L_next)`
-
-
-# Security Discussion
-
-If client and server authenticate one-another using the `AUTH_STRING` through an out-of-band
-mechanism, we believe that this handshake is resistant to an active man-in-the-middle attacker.
-The attacker, whether he/she plays the role of the client or server, is forced to commit to a
-public key before seeing the other-party's public key.
-
-The authentication string and next secret are computed in such a way that knowledge of one does
-not allow an attacker to compute the other. That is, if the attacker observed the `AUTH_STRING`
-(if it was shown on a monitor for example), the attacker could not compute `NEXT_SECRET`.
-Furthermore, both the authentication string and next secret depend on the full handshake
-transcript -- a manipulation of any handshake message by an adversary would change both the
- authentication string and the next secret. Note that although the last message is not directly
- included in the HKDF computation, it is included as part of the commitment sent in `M_1.`
-
-@shabsi pointed out that by having the `HKDF` info field have bits that also go into making the
-`PRK`, this violates some security proof. Those "shared" bits are the public keys that are sent
-in `M_2` and `M_3` and are also used to derive the DHS. Though the "proof" may
- not hold in theory, we do believe the security of the handshake is maintained in practice.
-
-A natural question may be why we didn't use
-[Short Authentication Strings](https://www.iacr.org/archive/crypto2005/36210303/36210303.pdf)
-(SAS). The answer is two-fold. First, traditional SAS does not incorporate a key exchange, only
-authentication; UKEY2 provides both. Second, the paper does not give concrete primitives,
-instead describing abstract functions such as `commit() `and `open()`. One concrete
-implementation of these functions would look similar to what UKEY2 does.
-
-Bruno Blanchet performed a formal proof of a simplified version of UKEY2.
-
-# Performance
-
-The messages are fairly compact. Running a test where the client sent a single commitment for a
-`P256_SHA512` cipher and the `next_protocol` was set to "`AES_256_CBC-HMAC_SHA256"`, the total
-size of the messages were:
-
-
-| Message | Length in Bytes |
-|:---------------|----------------:|
-|`ClientInit` | 136 |
-|`ServerInit` | 117 |
-|`ClientFinished`| 79 |
-
-
-# Checking out source code
-
-```
-git clone https://github.com/google/ukey2
-cd ukey2
-git submodule update --init --recursive
-```
-
-# Building and tesging C++ code
-
-## Build
-```
-cd <source root>
-mkdir build; cd build
-cmake -Dukey2_USE_LOCAL_PROTOBUF=ON -Dukey2_USE_LOCAL_ABSL=ON ..
-make
-```
-## Running C++ tests
-```
-cd <source root>/build
-ctest -V
-```
-
-# Buillding Java library and running Java Tests
-
-NOTE: c++ build must be completed as described above, before running java tests.
-This requirement exists because Java build runs a c++/java compatibility test, and
-this test depends on c++ test helper binary (found in build/src/main/cpp/test/securegcm/ukey2_test).
-Gradle build does not know how to build this artifact. Java test uses a relative
-path to the artifact, and expects tests to be run from <source root> as follows:
-
-Pre-reqs: gradle
-
-1. Create gradle wrapper for a specific gradle version.
-This project was built with Gradle-6.1.1.
-If you have an incompatible version of gradle it is recommended that
-you setup gradle wrapper first.
-1.1. The simplest is to run
-```
-cd <source root>
-gradle wrapper --gradle-version=6.1.1
-
-```
-
-1.2. If this fails, this is likely because current gradle version is unable to parse the build.gradle
-file. In this case, create an empty directory outside your project tree, and create a wrapper there.
-```
-mkdir -p $HOME/scratch/gradle-wrapper-611
-cd $HOME/scratch/gradle-wrapper-611
-gradle wrapper --gradle-version=6.1.1
-cp -a gradle gradlew gradlew.bat <source root>
-```
-
-2. Once you get gradle wrapper installed, run test command
-
-```
-cd <source root>
-./gradlew test -i
-```
-
-This will build and execute all the tests.
diff --git a/build.gradle b/build.gradle
deleted file mode 100644
index cbb2d2e..0000000
--- a/build.gradle
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright 2020 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
-//
-// https://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.
-
-apply plugin: 'java'
-apply plugin: 'com.google.protobuf'
-
-repositories {
- mavenCentral()
-}
-
-buildscript {
- repositories {
- mavenCentral()
- }
- dependencies {
- classpath 'com.google.protobuf:protobuf-gradle-plugin:0.9.4'
- }
-}
-
-dependencies {
- implementation group: 'com.google.truth.extensions', name: 'truth-java8-extension', version: '0.41'
- testImplementation group: 'com.google.guava', name: 'guava-testlib', version: '29.0-jre'
- testImplementation 'junit:junit:4.13'
- implementation "com.google.code.findbugs:jsr305:3.0.0"
- implementation "com.google.protobuf:protobuf-java:3.19.6"
- implementation "com.google.guava:guava:19.0"
-}
-
-sourceSets {
- main {
- java {
- srcDir 'src/main/java'
- srcDir 'build/generated/source/proto/main/java'
- }
- }
- test {
- java {
- srcDir 'src/main/javatest'
- srcDir 'build/generated/source/proto/main/java'
- }
- }
-}
-
-test {
- useJUnit()
-
- maxHeapSize = '1G'
-
- testLogging {
- outputs.upToDateWhen {false}
- showStandardStreams = true
- }
-}
diff --git a/cmake/local_build_protobuf.cmake b/cmake/local_build_protobuf.cmake
deleted file mode 100644
index 3a04d55..0000000
--- a/cmake/local_build_protobuf.cmake
+++ /dev/null
@@ -1,42 +0,0 @@
-# Copyright 2020 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
-#
-# https://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.
-
-if (NOT EXISTS ${TOOLS_INSTALL_PREFIX}/bin/protoc)
- set(PKG_BUILD_ROOT ${TOOLS_BUILD_ROOT}/protobuf)
- set(PKG_SRC_ROOT ${CMAKE_SOURCE_DIR}/third_party/protobuf)
- execute_process(
- COMMAND mkdir -p ${PKG_BUILD_ROOT}
- )
- execute_process(
- COMMAND cmake ${PKG_SRC_ROOT}/cmake
- WORKING_DIRECTORY ${PKG_BUILD_ROOT}
- )
- execute_process(
- COMMAND make -j${N_CPUS}
- WORKING_DIRECTORY ${PKG_BUILD_ROOT}
- )
- execute_process(
- COMMAND make check
- WORKING_DIRECTORY ${PKG_BUILD_ROOT}
- RESULT_VARIABLE test_exit_code
- ERROR_QUIET
- )
- if (NOT ${test_exit_code} EQUAL "0")
- message(FATAL_ERROR "Protobuf tests failed; can't use this protobuf")
- endif()
- execute_process(
- COMMAND /bin/bash -c "DESTDIR=${TOOLS_INSTALL_ROOT} make install"
- WORKING_DIRECTORY ${PKG_BUILD_ROOT}
- )
-endif()
diff --git a/cmake/local_build_setup.cmake b/cmake/local_build_setup.cmake
deleted file mode 100644
index a7917fe..0000000
--- a/cmake/local_build_setup.cmake
+++ /dev/null
@@ -1,26 +0,0 @@
-# Copyright 2020 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
-#
-# https://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(ProcessorCount)
-ProcessorCount(N_CPUS)
-
-if (N_CPUS EQUAL 0)
- set (N_CPUS 1)
-endif()
-
-set (TOOLS_ROOT ${CMAKE_BINARY_DIR}/stage)
-set (TOOLS_BUILD_ROOT ${TOOLS_ROOT}/build)
-set (TOOLS_INSTALL_ROOT ${TOOLS_ROOT}/install)
-set (TOOLS_INSTALL_PREFIX ${TOOLS_INSTALL_ROOT}/usr/local)
-set (CMAKE_FIND_ROOT_PATH ${TOOLS_INSTALL_ROOT})
diff --git a/cmake/proto_defs.cmake b/cmake/proto_defs.cmake
deleted file mode 100644
index aae0ce9..0000000
--- a/cmake/proto_defs.cmake
+++ /dev/null
@@ -1,42 +0,0 @@
-# Copyright 2020 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
-#
-# https://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.
-
-function(add_cc_proto_library NAME)
- set(single)
- set(multi_args PROTOS INCS DEPS)
- cmake_parse_arguments(PARSE_ARGV 1 args "" "${single}" "${multi_args}")
-
- protobuf_generate(
- PROTOS ${args_PROTOS}
- LANGUAGE cpp
- OUT_VAR ${NAME}_var
- )
-
- add_library(${NAME}
- ${${NAME}_var}
- )
-
- target_link_libraries(${NAME}
- PUBLIC
- ${Protobuf_LIBRARIES}
- ${args_DEPS}
- )
-
- target_include_directories(${NAME}
- PUBLIC
- ${Protobuf_INCLUDE_DIRS}
- ${args_INCS}
- ${CMAKE_CURRENT_BINARY_DIR}
- )
-endfunction()
diff --git a/src/main/CMakeLists.txt b/src/main/CMakeLists.txt
deleted file mode 100644
index 776826c..0000000
--- a/src/main/CMakeLists.txt
+++ /dev/null
@@ -1,18 +0,0 @@
-# Copyright 2020 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
-#
-# https://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.
-
-set(UKEY_SRC_ROOT ${CMAKE_CURRENT_LIST_DIR})
-set(UKEY_BINARY_ROOT ${CMAKE_CURRENT_BINARY_DIR})
-add_subdirectory(cpp)
-add_subdirectory(proto)
diff --git a/src/main/cpp/CMakeLists.txt b/src/main/cpp/CMakeLists.txt
deleted file mode 100644
index 919e096..0000000
--- a/src/main/cpp/CMakeLists.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-# Copyright 2020 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
-#
-# https://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_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)
-add_subdirectory(src/securegcm)
-add_subdirectory(test/securegcm)
diff --git a/src/main/cpp/include/securegcm/d2d_connection_context_v1.h b/src/main/cpp/include/securegcm/d2d_connection_context_v1.h
deleted file mode 100644
index 098e654..0000000
--- a/src/main/cpp/include/securegcm/d2d_connection_context_v1.h
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright 2020 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
-//
-// https://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 SECURITY_CRYPTAUTH_LIB_SECUREGCM_D2D_CONNECTION_CONTEXT_V1_H_
-#define SECURITY_CRYPTAUTH_LIB_SECUREGCM_D2D_CONNECTION_CONTEXT_V1_H_
-
-#include <memory>
-#include <string>
-
-#include "securemessage/crypto_ops.h"
-
-namespace securegcm {
-
-// The full context of a secure connection. This class has methods to encode and
-// decode messages that are to be sent to another device.
-//
-// This class should be kept compatible with the Java implementation in
-// java/com/google/security/cryptauth/lib/securegcm/D2DConnectionContextV1.java
-class D2DConnectionContextV1 {
- public:
- D2DConnectionContextV1(const securemessage::CryptoOps::SecretKey& encode_key,
- const securemessage::CryptoOps::SecretKey& decode_key,
- uint32_t encode_sequence_number,
- uint32_t decode_sequence_number);
-
- // Once the initiator and responder have negotiated a secret key, use this
- // method to encrypt and sign |payload|. Both initiator and responder devices
- // can use this message.
- //
- // On failure, nullptr is returned.
- std::unique_ptr<string> EncodeMessageToPeer(const string& payload);
-
- // Once the initiator and responder have negotiated a secret key, use this
- // method to decrypt and verify a |message| received from the other device.
- // Both initiator and responder devices can use this message.
- //
- // On failure, nullptr is returned.
- std::unique_ptr<string> DecodeMessageFromPeer(const string& message);
-
- // Returns a cryptographic digest (SHA256) of the session keys prepended by
- // the SHA256 hash of the ASCII string "D2D".
- //
- // On failure, nullptr is returned.
- std::unique_ptr<string> GetSessionUnique();
-
- // Creates a saved session that can be later used for resumption. Note,
- // this must be stored in a secure location.
- std::unique_ptr<string> SaveSession();
-
- // Parse a saved session info and attempt to construct a resumed context.
- //
- // The session info passed to this method should be one that was generated
- // by |SaveSession|.
- //
- // On failure, nullptr is returned.
- static std::unique_ptr<D2DConnectionContextV1> FromSavedSession(
- const string& savedSessionInfo);
-
- private:
- // The key used to encode payloads.
- const securemessage::CryptoOps::SecretKey encode_key_;
-
- // The key used to decode received messages.
- const securemessage::CryptoOps::SecretKey decode_key_;
-
- // The current sequence number for encoding.
- uint32_t encode_sequence_number_;
-
- // The current sequence number for decoding.
- uint32_t decode_sequence_number_;
-
- // A friend to access private variables for testing.
- friend class D2DConnectionContextV1Peer;
-};
-
-} // namespace securegcm
-
-#endif // SECURITY_CRYPTAUTH_LIB_SECUREGCM_D2D_CONNECTION_CONTEXT_V1_H_
diff --git a/src/main/cpp/include/securegcm/d2d_crypto_ops.h b/src/main/cpp/include/securegcm/d2d_crypto_ops.h
deleted file mode 100644
index eeeeb20..0000000
--- a/src/main/cpp/include/securegcm/d2d_crypto_ops.h
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright 2020 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
-//
-// https://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 SECURITY_CRYPTAUTH_LIB_SECUREGCM_D2D_CRYPTO_OPS_H_
-#define SECURITY_CRYPTAUTH_LIB_SECUREGCM_D2D_CRYPTO_OPS_H_
-
-#include <memory>
-#include <string>
-
-#include "proto/securegcm.pb.h"
-#include "securemessage/crypto_ops.h"
-
-namespace securegcm {
-
-// A collection of static utility methods for the Device to Device communication
-// (D2D) library.
-//
-// A class is used here in preference to a namespace to provide a closer
-// correspondence with the Java equivalent class:
-// //java/com/google/security/cryptauth/lib/securegcm/D2DCryptoOps.java
-class D2DCryptoOps {
- public:
- // Encapsulates a payload type specifier, and a corresponding message as the
- // raw payload.
- //
- // Note: Type is defined in securegcm.proto.
- class Payload {
- public:
- Payload(Type type, const std::string& message);
-
- Type type() const { return type_; }
-
- const std::string& message() const { return message_; }
-
- private:
- const Type type_;
- const std::string message_;
- };
-
- // The salt, SHA256 of "D2D".
- static const uint8_t kSalt[];
- static const size_t kSaltLength;
-
- // Used by a device to send a secure |Payload| to another device.
- static std::unique_ptr<std::string> SigncryptPayload(
- const Payload& payload,
- const securemessage::CryptoOps::SecretKey& secret_key);
-
- // Used by a device to recover a secure |Payload| sent by another device.
- static std::unique_ptr<Payload> VerifyDecryptPayload(
- const std::string& signcrypted_message,
- const securemessage::CryptoOps::SecretKey& secret_key);
-
- // Used to derive a distinct key for each initiator and responder from the
- // |master_key|. Use a different |purpose| for each role.
- static std::unique_ptr<securemessage::CryptoOps::SecretKey>
- DeriveNewKeyForPurpose(const securemessage::CryptoOps::SecretKey& master_key,
- const std::string& purpose);
-
- private:
- // Prevent instantiation.
- D2DCryptoOps();
-};
-
-} // namespace securegcm
-
-#endif // SECURITY_CRYPTAUTH_LIB_SECUREGCM_D2D_CRYPTO_OPS_H_
diff --git a/src/main/cpp/include/securegcm/java_util.h b/src/main/cpp/include/securegcm/java_util.h
deleted file mode 100644
index 8783af4..0000000
--- a/src/main/cpp/include/securegcm/java_util.h
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright 2020 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
-//
-// https://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.
-
-// Utility functions for Java-compatible operations.
-#ifndef SECURITY_CRYPTAUTH_LIB_SECUREGCM_JAVA_UTIL_H_
-#define SECURITY_CRYPTAUTH_LIB_SECUREGCM_JAVA_UTIL_H_
-
-#include "securemessage/byte_buffer.h"
-
-namespace securegcm {
-namespace java_util {
-
-// Perform multiplication with Java overflow semantics
-// (https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html):
-// If an integer multiplication overflows, then the result is the low-order
-// bits of the mathematical product as represented in some sufficiently
-// large two's-complement format.
-int32_t JavaMultiply(int32_t lhs, int32_t rhs);
-
-// Perform addition with Java overflow semantics:
-// (https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html):
-// If an integer addition overflows, then the result is the low-order bits of
-// the mathematical sum as represented in some sufficiently large
-// two's-complement format.
-int32_t JavaAdd(int32_t lhs, int32_t rhs);
-
-// To be compatible with the Java implementation, we need to use the same
-// algorithm as the Arrays#hashCode(byte[]) function in Java:
-// "The value returned by this method is the same value that would be obtained
-// by invoking the hashCode method on a List containing a sequence of Byte
-// instances representing the elements of a in the same order."
-//
-// According to List#hashCode(), this algorithm is:
-// int hashCode = 1;
-// for (Byte b : list) {
-// hashCode = 31 * hashCode + (b == null ? b : b.hashCode());
-// }
-//
-// Finally, Byte#hashCode() is defined as "equal to the result of invoking
-// Byte#intValue()".
-int32_t JavaHashCode(const securemessage::ByteBuffer& byte_buffer);
-
-} // namespace java_util
-} // namespace securegcm
-
-#endif // SECURITY_CRYPTAUTH_LIB_SECUREGCM_JAVA_UTIL_H_
diff --git a/src/main/cpp/include/securegcm/ukey2_handshake.h b/src/main/cpp/include/securegcm/ukey2_handshake.h
deleted file mode 100644
index 8455fd7..0000000
--- a/src/main/cpp/include/securegcm/ukey2_handshake.h
+++ /dev/null
@@ -1,263 +0,0 @@
-// Copyright 2020 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
-//
-// https://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 SECURITY_CRYPTAUTH_LIB_SECUREGCM_UKEY2_HANDSHAKE_H_
-#define SECURITY_CRYPTAUTH_LIB_SECUREGCM_UKEY2_HANDSHAKE_H_
-
-#include <map>
-#include <memory>
-
-#include "proto/ukey.pb.h"
-#include "securegcm/d2d_connection_context_v1.h"
-#include "securemessage/crypto_ops.h"
-
-namespace securegcm {
-
-// Implements UKEY2 and produces a |D2DConnectionContextV1|.
-// This class should be kept compatible with the Java implementation in
-// //java/com/google/security/cryptauth/lib/securegcm/Ukey2Handshake.java
-//
-// For usage examples, see ukey2_shell.cc. This file contains a shell exercising
-// both the initiator and responder handshake roles.
-class UKey2Handshake {
- public:
- // Handshake states:
- // kInProgress:
- // The handshake is in progress, caller should use
- // |GetNextHandshakeMessage()| and |ParseHandshakeMessage()| to continue
- // the handshake.
- //
- // kVerificationNeeded:
- // The handshake is complete, but pending verification of the
- // authentication string. Clients should use |GetVerificationString()|
- // to get the verification string and use out-of-band methods to
- // authenticate the handshake.
- //
- // kVerificationInProgress:
- // The handshake is complete, verification string has been generated,
- // but has not been confirmed. After authenticating the handshake
- // out-of-band, use |VerifyHandshake()| to mark the handshake as
- // verified.
- //
- // kFinished:
- // The handshake is finished, and the caller can use
- // |ToConnectionContext()| to produce a |D2DConnectionContextV1|.
- //
- // kAlreadyUsed:
- // The hanshake has already been used and should be destroyed.
- //
- // kError:
- // The handshake produced an error and should be destroyed.
- enum class State {
- kInProgress,
- kVerificationNeeded,
- kVerificationInProgress,
- kFinished,
- kAlreadyUsed,
- kError,
- };
-
- // Currently implemented UKEY2 handshake ciphers. Each cipher is a tuple
- // consisting of a key negotiation cipher and a hash function used for a
- // commitment. Currently the ciphers are:
- // +-----------------------------------------------------+
- // | Enum | Key negotiation | Hash function |
- // +-------------+-----------------------+---------------+
- // | P256_SHA512 | ECDH using NIST P-256 | SHA512 |
- // +-----------------------------------------------------+
- //
- // Note that these should correspond to values in
- // device_to_device_messages.proto.
- enum class HandshakeCipher : int {
- // TODO(aczeskis): add CURVE25519_SHA512
-
- P256_SHA512 = securegcm::P256_SHA512,
- };
-
- // Creates a |UKey2Handshake| with a particular |cipher| that can be used by
- // an initiator / client.
- static std::unique_ptr<UKey2Handshake> ForInitiator(HandshakeCipher cipher);
-
- // Creates a |UKey2Handshake| with a particular |cipher| that can be used by
- // a responder / server.
- static std::unique_ptr<UKey2Handshake> ForResponder(HandshakeCipher cipher);
-
- // Returns the current state of the handshake.
- State GetHandshakeState() const;
-
- // Returns the last error message. Empty string if there was no error.
- const string& GetLastError() const;
-
- // Gets the next handshake message suitable for sending on the wire.
- // If |nullptr| is returned, check |GetLastError()| for the error message.
- std::unique_ptr<string> GetNextHandshakeMessage();
-
- // Parses the given |handshake_message|, updating the internal state.
- struct ParseResult {
- // True if |handshake_message| is parsed successfully. If |false|, call
- // |GetLastError()| for the error message.
- bool success;
-
- // May be set if parsing fails. This value should be sent to the remote
- // device before disconnecting.
- std::unique_ptr<string> alert_to_send;
- };
- ParseResult ParseHandshakeMessage(const string& handshake_message);
-
- // Returns an authentication string suitable for authenticating the handshake
- // out-of-band. Note that the authentication string can be short (e.g., a 6
- // digit visual confirmation code).
- //
- // Note: This should only be called when the state returned from
- // |GetHandshakeState()| is |State::VERIFICATION_NEEDED|, which means this can
- // only be called once.
- //
- // |byte_length|: The length of the output. Min length is 1; max length is 32.
- // If |nullptr| is returned, check |GetLastError()| for the error message.
- std::unique_ptr<string> GetVerificationString(int byte_length);
-
- // Invoked to let the handshake state machine know that caller has validated
- // the authentication string obtained via |GetVerificationString()|.
- // Note: This should only be called when the state returned by
- // |GetHandshakeState()| is |State::VERIFICATION_IN_PROGRESS|.
- //
- // If |false| is returned, check |GetLastError()| for the error message.
- bool VerifyHandshake();
-
- // Can be called to generate a |D2DConnectionContextV1|. Returns nullptr on
- // failure.
- // Note: This should only be called when the state returned by
- // |GetHandshakeState()| is |State::FINISHED|.
- //
- // If |nullptr| is returned, check |GetLastError()| for the error message.
- std::unique_ptr<D2DConnectionContextV1> ToConnectionContext();
-
- private:
- // Enums for internal state machinery.
- enum class InternalState : int {
- CLIENT_START,
- CLIENT_WAITING_FOR_SERVER_INIT,
- CLIENT_AFTER_SERVER_INIT,
-
- // Responder/server state
- SERVER_START,
- SERVER_AFTER_CLIENT_INIT,
- SERVER_WAITING_FOR_CLIENT_FINISHED,
-
- // Common completion state
- HANDSHAKE_VERIFICATION_NEEDED,
- HANDSHAKE_VERIFICATION_IN_PROGRESS,
- HANDSHAKE_FINISHED,
- HANDSHAKE_ALREADY_USED,
- HANDSHAKE_ERROR,
- };
-
- // Helps us remember our role in the handshake.
- enum class HandshakeRole {
- CLIENT,
- SERVER
- };
-
- // Prevent public instantiation. Callers should use |ForInitiator()| or
- // |ForResponder()|.
- UKey2Handshake(InternalState state, HandshakeCipher cipher);
-
- // Attempts to parse Ukey2ClientInit, wrapped inside a Ukey2Message.
- // See go/ukey2 for details.
- ParseResult ParseClientInitUkey2Message(const string& handshake_message);
-
- // Attempts to parse Ukey2ServerInit, wrapped inside a Ukey2Message.
- // See go/ukey2 for details.
- ParseResult ParseServerInitUkey2Message(const string& handshake_message);
-
- // Attempts to parse Ukey2ClientFinish, wrapped inside a Ukey2Message.
- // See go/ukey2 for details.
- ParseResult ParseClientFinishUkey2Message(const string& handshake_message);
-
- // Convenience function to set |last_error_| and create a ParseResult with a
- // given alert.
- ParseResult CreateFailedResultWithAlert(Ukey2Alert::AlertType alert_type,
- const string& error_message);
-
- // Convenience function to set |last_error_| and create a failed ParseResult
- // without an alert.
- ParseResult CreateFailedResultWithoutAlert(const string& error_message);
-
- // Convenience function to create a successful ParseResult.
- ParseResult CreateSuccessResult();
-
- // Verifies that the peer's commitment stored in |peer_commitment_| is the
- // same as that obtained from |handshake_message|.
- bool VerifyCommitment(const string& handshake_message);
-
- // Generates a commitment for the P256_SHA512 cipher.
- std::unique_ptr<Ukey2ClientInit::CipherCommitment>
- GenerateP256Sha512Commitment();
-
- // Creates a serialized Ukey2Message, wrapping an inner ClientInit message.
- std::unique_ptr<string> MakeClientInitUkey2Message();
-
- // Creates a serialized Ukey2Message, wrapping an inner ServerInit message.
- std::unique_ptr<string> MakeServerInitUkey2Message();
-
- // Creates a serialized Ukey2Message of a given |type|, wrapping |data|.
- std::unique_ptr<string> MakeUkey2Message(Ukey2Message::Type type,
- const string& data);
-
- // Called when an error occurs to set |handshake_state_| and |last_error_|.
- void SetError(const string& error_message);
-
- // The current state of the handshake.
- InternalState handshake_state_;
-
- // The cipher to use for the handshake.
- const HandshakeCipher handshake_cipher_;
-
- // The role to perform, i.e. client or server.
- const HandshakeRole handshake_role_;
-
- // A newly generated key-pair for this handshake.
- std::unique_ptr<securemessage::CryptoOps::KeyPair> our_key_pair_;
-
- // The peer's public key retrieved from a handshake message.
- std::unique_ptr<securemessage::CryptoOps::PublicKey> their_public_key_;
-
- // The secret key derived from |our_key_pair_| and |their_public_key_|.
- std::unique_ptr<securemessage::CryptoOps::SecretKey> derived_secret_key_;
-
- // The raw bytes of the Ukey2ClientInit, wrapped inside a Ukey2Message.
- // Empty string if not initialized.
- string wrapped_client_init_;
-
- // The raw bytes of the Ukey2ServerInit, wrapped inside a Ukey2Message.
- // Empty string if not initialized.
- string wrapped_server_init_;
-
- // The commitment of the peer retrieved from a handshake message. Empty string
- // if not initialized.
- string peer_commitment_;
-
- // Map from ciphers to the raw bytes of message 3 (which is a wrapped
- // Ukey2ClientFinished message).
- // Note: Currently only one cipher is supported, so at most one entry exists
- // in this map.
- std::map<HandshakeCipher, string> raw_message3_map_;
-
- // Contains the last error message.
- string last_error_;
-};
-
-} // namespace securegcm
-
-#endif // SECURITY_CRYPTAUTH_LIB_SECUREGCM_UKEY2_HANDSHAKE_H_
diff --git a/src/main/cpp/src/securegcm/CMakeLists.txt b/src/main/cpp/src/securegcm/CMakeLists.txt
deleted file mode 100644
index b562756..0000000
--- a/src/main/cpp/src/securegcm/CMakeLists.txt
+++ /dev/null
@@ -1,47 +0,0 @@
-# Copyright 2020 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
-#
-# https://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.
-
-add_library(ukey2 STATIC
- d2d_connection_context_v1.cc
- d2d_crypto_ops.cc
- java_util.cc
- ukey2_handshake.cc
-)
-
-target_include_directories(ukey2
- PUBLIC
- ${PROJECT_SOURCE_DIR}/src/main/cpp/include
-)
-
-target_link_libraries(ukey2
- PUBLIC
- proto_device_to_device_messages_cc_proto
- proto_securegcm_cc_proto
- proto_ukey_cc_proto
- securemessage
-)
-
-add_executable(ukey2_shell
- ukey2_shell.cc
-)
-
-target_link_libraries(ukey2_shell
- PUBLIC
- securemessage
- ukey2
- absl::base
- absl::container
- absl::flags
- absl::flags_parse
-)
diff --git a/src/main/cpp/src/securegcm/d2d_connection_context_v1.cc b/src/main/cpp/src/securegcm/d2d_connection_context_v1.cc
deleted file mode 100644
index 8a9a612..0000000
--- a/src/main/cpp/src/securegcm/d2d_connection_context_v1.cc
+++ /dev/null
@@ -1,228 +0,0 @@
-// Copyright 2020 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
-//
-// https://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 "securegcm/d2d_connection_context_v1.h"
-
-#include <limits>
-#include <sstream>
-
-#include "proto/device_to_device_messages.pb.h"
-#include "proto/securegcm.pb.h"
-#include "securegcm/d2d_crypto_ops.h"
-#include "securegcm/java_util.h"
-#include "securemessage/secure_message_builder.h"
-#include "securemessage/util.h"
-
-namespace securegcm {
-
-using securemessage::CryptoOps;
-using securemessage::ByteBuffer;
-using securemessage::Util;
-
-namespace {
-
-// Fields to fill in the GcmMetadata proto.
-const Type kGcmMetadataType = DEVICE_TO_DEVICE_MESSAGE;
-
-// Represents the version of this context.
-const uint8_t kProtocolVersion = 1;
-
-// The following represent the starting positions of the each entry within
-// the string representation of this D2DConnectionContextV1.
-//
-// The saved session has a 1 byte protocol version, two 4 byte sequence numbers,
-// and two 32 byte AES keys: (1 + 4 + 4 + 32 + 32 = 73).
-
-// The two sequence numbers are 4 bytes each.
-const int kSequenceNumberLength = 4;
-
-// 32 byte AES keys.
-const int kAesKeyLength = 32;
-
-// The encode sequence number starts at 1 to account for the 1 byte version
-// number.
-const int kEncodeSequenceStart = 1;
-const int kEncodeSequenceEnd = kEncodeSequenceStart + kSequenceNumberLength;
-
-const int kDecodeSequenceStart = kEncodeSequenceEnd;
-const int kDecodeSequenceEnd = kDecodeSequenceStart + kSequenceNumberLength;
-
-const int kEncodeKeyStart = kDecodeSequenceEnd;
-const int kEncodeKeyEnd = kEncodeKeyStart + kAesKeyLength;
-
-const int kDecodeKeyStart = kEncodeKeyEnd;
-const int kSavedSessionLength = kDecodeKeyStart + kAesKeyLength;
-
-// Convenience function to creates a DeviceToDeviceMessage proto with |payload|
-// and |sequence_number|.
-DeviceToDeviceMessage CreateDeviceToDeviceMessage(const std::string& payload,
- uint32_t sequence_number) {
- DeviceToDeviceMessage device_to_device_message;
- device_to_device_message.set_sequence_number(sequence_number);
- device_to_device_message.set_message(payload);
- return device_to_device_message;
-}
-
-// Convert 4 bytes in big-endian representation into an unsigned int.
-uint32_t BytesToUnsignedInt(std::vector<uint8_t> bytes) {
- return bytes[0] << 24 | bytes[1] << 12 | bytes[2] << 8 | bytes[3];
-}
-
-// Convert an unsigned int into a 4 byte big-endian representation.
-std::vector<uint8_t> UnsignedIntToBytes(uint32_t val) {
- return {static_cast<uint8_t>(val >> 24), static_cast<uint8_t>(val >> 12),
- static_cast<uint8_t>(val >> 8), static_cast<uint8_t>(val)};
-}
-
-} // namespace
-
-D2DConnectionContextV1::D2DConnectionContextV1(
- const CryptoOps::SecretKey& encode_key,
- const CryptoOps::SecretKey& decode_key, uint32_t encode_sequence_number,
- uint32_t decode_sequence_number)
- : encode_key_(encode_key),
- decode_key_(decode_key),
- encode_sequence_number_(encode_sequence_number),
- decode_sequence_number_(decode_sequence_number) {}
-
-std::unique_ptr<std::string> D2DConnectionContextV1::EncodeMessageToPeer(
- const std::string& payload) {
- encode_sequence_number_++;
- const DeviceToDeviceMessage message =
- CreateDeviceToDeviceMessage(payload, encode_sequence_number_);
-
- const D2DCryptoOps::Payload payload_with_type(kGcmMetadataType,
- message.SerializeAsString());
- return D2DCryptoOps::SigncryptPayload(payload_with_type, encode_key_);
-}
-
-std::unique_ptr<std::string> D2DConnectionContextV1::DecodeMessageFromPeer(
- const std::string& message) {
- std::unique_ptr<D2DCryptoOps::Payload> payload =
- D2DCryptoOps::VerifyDecryptPayload(message, decode_key_);
- if (!payload) {
- Util::LogError("DecodeMessageFromPeer: Failed to verify message.");
- return nullptr;
- }
-
- if (kGcmMetadataType != payload->type()) {
- Util::LogError("DecodeMessageFromPeer: Wrong message type in D2D message.");
- return nullptr;
- }
-
- DeviceToDeviceMessage d2d_message;
- if (!d2d_message.ParseFromString(payload->message())) {
- Util::LogError("DecodeMessageFromPeer: Unable to parse D2D message proto.");
- return nullptr;
- }
-
- decode_sequence_number_++;
- if (d2d_message.sequence_number() != decode_sequence_number_) {
- std::ostringstream stream;
- stream << "DecodeMessageFromPeer: Seqno in D2D message ("
- << d2d_message.sequence_number()
- << ") does not match expected seqno (" << decode_sequence_number_
- << ").";
- Util::LogError(stream.str());
- return nullptr;
- }
-
- return std::unique_ptr<std::string>(d2d_message.release_message());
-}
-
-std::unique_ptr<std::string> D2DConnectionContextV1::GetSessionUnique() {
- const ByteBuffer encode_key_data = encode_key_.data();
- const ByteBuffer decode_key_data = decode_key_.data();
- const int32_t encode_key_hash = java_util::JavaHashCode(encode_key_data);
- const int32_t decode_key_hash = java_util::JavaHashCode(decode_key_data);
-
- const ByteBuffer& first_buffer =
- encode_key_hash < decode_key_hash ? encode_key_data : decode_key_data;
- const ByteBuffer& second_buffer =
- encode_key_hash < decode_key_hash ? decode_key_data : encode_key_data;
-
- ByteBuffer data_to_hash(D2DCryptoOps::kSalt, D2DCryptoOps::kSaltLength);
- data_to_hash = ByteBuffer::Concat(data_to_hash, first_buffer);
- data_to_hash = ByteBuffer::Concat(data_to_hash, second_buffer);
-
- std::unique_ptr<ByteBuffer> hash = CryptoOps::Sha256(data_to_hash);
- if (!hash) {
- Util::LogError("GetSessionUnique: SHA-256 hash failed.");
- return nullptr;
- }
-
- return std::unique_ptr<std::string>(new std::string(hash->String()));
-}
-
-// Structure of saved session is:
-//
-// +---------------------------------------------------------------------------+
-// | 1 Byte | 4 Bytes | 4 Bytes | 32 Bytes | 32 Bytes |
-// +---------------------------------------------------------------------------+
-// | Version | encode seq number | decode seq number | encode key | decode key |
-// +---------------------------------------------------------------------------+
-//
-// The sequence numbers are represented in big-endian.
-std::unique_ptr<std::string> D2DConnectionContextV1::SaveSession() {
- ByteBuffer byteBuffer = ByteBuffer(&kProtocolVersion, static_cast<size_t>(1));
-
- // Append encode sequence number.
- std::vector<uint8_t> encode_sequence_number_bytes =
- UnsignedIntToBytes(encode_sequence_number_);
- for (int i = 0; i < encode_sequence_number_bytes.size(); i++) {
- byteBuffer.Append(static_cast<size_t>(1), encode_sequence_number_bytes[i]);
- }
-
- // Append decode sequence number.
- std::vector<uint8_t> decode_sequence_number_bytes =
- UnsignedIntToBytes(decode_sequence_number_);
- for (int i = 0; i < decode_sequence_number_bytes.size(); i++) {
- byteBuffer.Append(static_cast<size_t>(1), decode_sequence_number_bytes[i]);
- }
-
- // Append encode key.
- byteBuffer = ByteBuffer::Concat(byteBuffer, encode_key_.data());
-
- // Append decode key.
- byteBuffer = ByteBuffer::Concat(byteBuffer, decode_key_.data());
-
- return std::unique_ptr<std::string>(new std::string(byteBuffer.String()));
-}
-
-// static.
-std::unique_ptr<D2DConnectionContextV1>
-D2DConnectionContextV1::FromSavedSession(const std::string& savedSessionInfo) {
- ByteBuffer byteBuffer = ByteBuffer(savedSessionInfo);
-
- if (byteBuffer.size() != kSavedSessionLength) {
- return nullptr;
- }
-
- uint32_t encode_sequence_number = BytesToUnsignedInt(
- byteBuffer.SubArray(kEncodeSequenceStart, kEncodeSequenceEnd)->Vector());
- uint32_t decode_sequence_number = BytesToUnsignedInt(
- byteBuffer.SubArray(kDecodeSequenceStart, kDecodeSequenceEnd)->Vector());
-
- const CryptoOps::SecretKey encode_key =
- CryptoOps::SecretKey(*byteBuffer.SubArray(kEncodeKeyStart, kAesKeyLength),
- CryptoOps::KeyAlgorithm::AES_256_KEY);
- const CryptoOps::SecretKey decode_key =
- CryptoOps::SecretKey(*byteBuffer.SubArray(kDecodeKeyStart, kAesKeyLength),
- CryptoOps::KeyAlgorithm::AES_256_KEY);
-
- return std::unique_ptr<D2DConnectionContextV1>(new D2DConnectionContextV1(
- encode_key, decode_key, encode_sequence_number, decode_sequence_number));
-}
-
-} // namespace securegcm
diff --git a/src/main/cpp/src/securegcm/d2d_crypto_ops.cc b/src/main/cpp/src/securegcm/d2d_crypto_ops.cc
deleted file mode 100644
index 49f0b85..0000000
--- a/src/main/cpp/src/securegcm/d2d_crypto_ops.cc
+++ /dev/null
@@ -1,151 +0,0 @@
-// Copyright 2020 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
-//
-// https://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 "securegcm/d2d_crypto_ops.h"
-
-#include <sstream>
-
-#include "securemessage/secure_message_builder.h"
-#include "securemessage/secure_message_parser.h"
-#include "securemessage/util.h"
-
-namespace securegcm {
-
-using securemessage::CryptoOps;
-using securemessage::HeaderAndBody;
-using securemessage::SecureMessage;
-using securemessage::SecureMessageBuilder;
-using securemessage::SecureMessageParser;
-using securemessage::Util;
-
-namespace {
-
-// The current protocol version.
-const int kSecureGcmProtocolVersion = 1;
-
-// The number of bytes in an expected AES256 key.
-const int kAes256KeyLength = 32;
-}
-
-// static.
-const uint8_t D2DCryptoOps::kSalt[] = {
- 0x82, 0xAA, 0x55, 0xA0, 0xD3, 0x97, 0xF8, 0x83, 0x46, 0xCA, 0x1C,
- 0xEE, 0x8D, 0x39, 0x09, 0xB9, 0x5F, 0x13, 0xFA, 0x7D, 0xEB, 0x1D,
- 0x4A, 0xB3, 0x83, 0x76, 0xB8, 0x25, 0x6D, 0xA8, 0x55, 0x10};
-
-// static.
-const size_t D2DCryptoOps::kSaltLength = sizeof(D2DCryptoOps::kSalt);
-
-D2DCryptoOps::Payload::Payload(Type type, const string& message)
- : type_(type), message_(message) {}
-
-D2DCryptoOps::D2DCryptoOps() {}
-
-// static.
-std::unique_ptr<string> D2DCryptoOps::SigncryptPayload(
- const Payload& payload, const CryptoOps::SecretKey& secret_key) {
- GcmMetadata gcm_metadata;
- gcm_metadata.set_type(payload.type());
- gcm_metadata.set_version(kSecureGcmProtocolVersion);
-
- SecureMessageBuilder builder;
- builder.SetPublicMetadata(gcm_metadata.SerializeAsString());
-
- std::unique_ptr<SecureMessage> secure_message =
- builder.BuildSignCryptedMessage(secret_key, CryptoOps::HMAC_SHA256,
- secret_key, CryptoOps::AES_256_CBC,
- payload.message());
- if (!secure_message) {
- Util::LogError("Unable to encrypt payload.");
- return nullptr;
- }
-
- return std::unique_ptr<string>(
- new string(secure_message->SerializeAsString()));
-}
-
-// static.
-std::unique_ptr<D2DCryptoOps::Payload> D2DCryptoOps::VerifyDecryptPayload(
- const string& signcrypted_message, const CryptoOps::SecretKey& secret_key) {
- SecureMessage secure_message;
- if (!secure_message.ParseFromString(signcrypted_message)) {
- Util::LogError("VerifyDecryptPayload: error parsing SecureMessage.");
- return nullptr;
- }
-
- std::unique_ptr<HeaderAndBody> header_and_body =
- SecureMessageParser::ParseSignCryptedMessage(
- secure_message, secret_key, CryptoOps::HMAC_SHA256, secret_key,
- CryptoOps::AES_256_CBC, string() /* associated_data */);
- if (!header_and_body) {
- Util::LogError("VerifyDecryptPayload: error verifying SecureMessage.");
- return nullptr;
- }
-
- if (!header_and_body->header().has_public_metadata()) {
- Util::LogError("VerifyDecryptPayload: no public metadata in header.");
- return nullptr;
- }
-
- GcmMetadata metadata;
- if (!metadata.ParseFromString(header_and_body->header().public_metadata())) {
- Util::LogError("VerifyDecryptPayload: Failed to parse GcmMetadata.");
- return nullptr;
- }
-
- if (metadata.version() != kSecureGcmProtocolVersion) {
- std::ostringstream stream;
- stream << "VerifyDecryptPayload: Unsupported protocol version "
- << metadata.version();
- Util::LogError(stream.str());
- return nullptr;
- }
-
- return std::unique_ptr<Payload>(
- new Payload(metadata.type(), header_and_body->body()));
-}
-
-// static.
-std::unique_ptr<CryptoOps::SecretKey> D2DCryptoOps::DeriveNewKeyForPurpose(
- const securemessage::CryptoOps::SecretKey& master_key,
- const string& purpose) {
- if (master_key.data().size() != kAes256KeyLength) {
- Util::LogError("DeriveNewKeyForPurpose: Invalid master_key length.");
- return nullptr;
- }
-
- if (purpose.empty()) {
- Util::LogError("DeriveNewKeyForPurpose: purpose is empty.");
- return nullptr;
- }
-
- std::unique_ptr<string> raw_derived_key = CryptoOps::Hkdf(
- master_key.data().String(),
- string(reinterpret_cast<const char *>(kSalt), kSaltLength),
- purpose);
- if (!raw_derived_key) {
- Util::LogError("DeriveNewKeyForPurpose: hkdf failed.");
- return nullptr;
- }
-
- if (raw_derived_key->size() != kAes256KeyLength) {
- Util::LogError("DeriveNewKeyForPurpose: Unexpected size of derived key.");
- return nullptr;
- }
-
- return std::unique_ptr<CryptoOps::SecretKey>(
- new CryptoOps::SecretKey(*raw_derived_key, CryptoOps::AES_256_KEY));
-}
-
-} // namespace securegcm
diff --git a/src/main/cpp/src/securegcm/java_util.cc b/src/main/cpp/src/securegcm/java_util.cc
deleted file mode 100644
index 1ce4d7b..0000000
--- a/src/main/cpp/src/securegcm/java_util.cc
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright 2020 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
-//
-// https://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 "securegcm/java_util.h"
-
-#include <cstring>
-
-namespace securegcm {
-namespace java_util {
-
-namespace {
-
-// Returns the lower 32-bits of a int64_t |value| as an int32_t.
-int32_t Lower32Bits(int64_t value) {
- const uint32_t lower_bits = static_cast<uint32_t>(value & 0xFFFFFFFF);
- int32_t return_value;
- std::memcpy(&return_value, &lower_bits, sizeof(uint32_t));
- return return_value;
-}
-
-} // namespace
-
-int32_t JavaMultiply(int32_t lhs, int32_t rhs) {
- // Multiplication guaranteed to fit in int64_t, range from [2^63, 2^63 - 1].
- // Minimum value is (-2^31)^2 = 2^62.
- const int64_t result = static_cast<int64_t>(lhs) * static_cast<int64_t>(rhs);
- return Lower32Bits(result);
-}
-
-int32_t JavaAdd(int32_t lhs, int32_t rhs) {
- const int64_t result = static_cast<int64_t>(lhs) + static_cast<int64_t>(rhs);
- return Lower32Bits(result);
-}
-
-int32_t JavaHashCode(const securemessage::ByteBuffer& byte_buffer) {
- const string bytes = byte_buffer.String();
- int32_t hash_code = 1;
- for (const int8_t byte : bytes) {
- int32_t int_value = static_cast<int32_t>(byte);
- // Java relies on the overflow/underflow behaviour of arithmetic operations,
- // which is undefined in C++, so we call our own Java-compatible versions of
- // + and * here.
- hash_code = JavaAdd(JavaMultiply(31, hash_code), int_value);
- }
- return hash_code;
-}
-
-} // namespace java_util
-} // namespace securegcm
diff --git a/src/main/cpp/src/securegcm/ukey2_handshake.cc b/src/main/cpp/src/securegcm/ukey2_handshake.cc
deleted file mode 100644
index dc5c131..0000000
--- a/src/main/cpp/src/securegcm/ukey2_handshake.cc
+++ /dev/null
@@ -1,715 +0,0 @@
-// Copyright 2020 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
-//
-// https://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 "securegcm/ukey2_handshake.h"
-
-#include <sstream>
-
-#include "securegcm/d2d_crypto_ops.h"
-#include "securemessage/public_key_proto_util.h"
-
-namespace securegcm {
-
-using securemessage::ByteBuffer;
-using securemessage::CryptoOps;
-using securemessage::GenericPublicKey;
-using securemessage::PublicKeyProtoUtil;
-
-namespace {
-
-// Salt value used to derive client and server keys for next protocol.
-const char kUkey2HkdfSalt[] = "UKEY2 v1 next";
-
-// Salt value used to derive verification string.
-const char kUkey2VerificationStringSalt[] = "UKEY2 v1 auth";
-
-// Maximum version of the handshake supported by this class.
-const uint32_t kVersion = 1;
-
-// Random nonce is fixed at 32 bytes (as per go/ukey2).
-const uint32_t kNonceLengthInBytes = 32;
-
-// Currently, we only support one next protocol.
-const char kNextProtocol[] = "AES_256_CBC-HMAC_SHA256";
-
-// Creates the appropriate KeyPair for |cipher|.
-std::unique_ptr<CryptoOps::KeyPair> GenerateKeyPair(
- UKey2Handshake::HandshakeCipher cipher) {
- switch (cipher) {
- case UKey2Handshake::HandshakeCipher::P256_SHA512:
- return CryptoOps::GenerateEcP256KeyPair();
- default:
- return nullptr;
- }
-}
-
-// Parses a CryptoOps::PublicKey from a serialized GenericPublicKey.
-std::unique_ptr<securemessage::CryptoOps::PublicKey> ParsePublicKey(
- const string& serialized_generic_public_key) {
- GenericPublicKey generic_public_key;
- if (!generic_public_key.ParseFromString(serialized_generic_public_key)) {
- return nullptr;
- }
- return PublicKeyProtoUtil::ParsePublicKey(generic_public_key);
-}
-
-} // namespace
-
-// static.
-std::unique_ptr<UKey2Handshake> UKey2Handshake::ForInitiator(
- HandshakeCipher cipher) {
- return std::unique_ptr<UKey2Handshake>(
- new UKey2Handshake(InternalState::CLIENT_START, cipher));
-}
-
-// static.
-std::unique_ptr<UKey2Handshake> UKey2Handshake::ForResponder(
- HandshakeCipher cipher) {
- return std::unique_ptr<UKey2Handshake>(
- new UKey2Handshake(InternalState::SERVER_START, cipher));
-}
-
-UKey2Handshake::UKey2Handshake(InternalState state, HandshakeCipher cipher)
- : handshake_state_(state),
- handshake_cipher_(cipher),
- handshake_role_(state == InternalState::CLIENT_START
- ? HandshakeRole::CLIENT
- : HandshakeRole::SERVER),
- our_key_pair_(GenerateKeyPair(cipher)) {}
-
-UKey2Handshake::State UKey2Handshake::GetHandshakeState() const {
- switch (handshake_state_) {
- case InternalState::CLIENT_START:
- case InternalState::CLIENT_WAITING_FOR_SERVER_INIT:
- case InternalState::CLIENT_AFTER_SERVER_INIT:
- case InternalState::SERVER_START:
- case InternalState::SERVER_AFTER_CLIENT_INIT:
- case InternalState::SERVER_WAITING_FOR_CLIENT_FINISHED:
- // Fallthrough intended -- these are all in-progress states.
- return State::kInProgress;
- case InternalState::HANDSHAKE_VERIFICATION_NEEDED:
- return State::kVerificationNeeded;
- case InternalState::HANDSHAKE_VERIFICATION_IN_PROGRESS:
- return State::kVerificationInProgress;
- case InternalState::HANDSHAKE_FINISHED:
- return State::kFinished;
- case InternalState::HANDSHAKE_ALREADY_USED:
- return State::kAlreadyUsed;
- case InternalState::HANDSHAKE_ERROR:
- return State::kError;
- default:
- // Unreachable.
- return State::kError;
- }
-}
-
-const string& UKey2Handshake::GetLastError() const {
- return last_error_;
-}
-
-std::unique_ptr<string> UKey2Handshake::GetNextHandshakeMessage() {
- switch (handshake_state_) {
- case InternalState::CLIENT_START: {
- std::unique_ptr<string> client_init = MakeClientInitUkey2Message();
- if (!client_init) {
- // |last_error_| is already set.
- return nullptr;
- }
-
- wrapped_client_init_ = *client_init;
- handshake_state_ = InternalState::CLIENT_WAITING_FOR_SERVER_INIT;
- return client_init;
- }
-
- case InternalState::SERVER_AFTER_CLIENT_INIT: {
- std::unique_ptr<string> server_init = MakeServerInitUkey2Message();
- if (!server_init) {
- // |last_error_| is already set.
- return nullptr;
- }
-
- wrapped_server_init_ = *server_init;
- handshake_state_ = InternalState::SERVER_WAITING_FOR_CLIENT_FINISHED;
- return server_init;
- }
-
- case InternalState::CLIENT_AFTER_SERVER_INIT: {
- // Make sure we have a message 3 for the chosen cipher.
- if (raw_message3_map_.count(handshake_cipher_) == 0) {
- std::ostringstream stream;
- stream << "Client state is CLIENT_AFTER_SERVER_INIT, and cipher is "
- << static_cast<int>(handshake_cipher_)
- << ", but no corresponding raw "
- << "[Client Finished] message has been generated.";
- SetError(stream.str());
- return nullptr;
- }
- handshake_state_ = InternalState::HANDSHAKE_VERIFICATION_NEEDED;
- return std::unique_ptr<string>(
- new string(raw_message3_map_[handshake_cipher_]));
- }
-
- default: {
- std::ostringstream stream;
- stream << "Cannot get next message in state "
- << static_cast<int>(handshake_state_);
- SetError(stream.str());
- return nullptr;
- }
- }
-}
-
-UKey2Handshake::ParseResult
-UKey2Handshake::ParseHandshakeMessage(const string& handshake_message) {
- switch (handshake_state_) {
- case InternalState::SERVER_START:
- return ParseClientInitUkey2Message(handshake_message);
- case InternalState::CLIENT_WAITING_FOR_SERVER_INIT:
- return ParseServerInitUkey2Message(handshake_message);
- case InternalState::SERVER_WAITING_FOR_CLIENT_FINISHED:
- return ParseClientFinishUkey2Message(handshake_message);
- default:
- std::ostringstream stream;
- stream << "Cannot parse message in state "
- << static_cast<int>(handshake_state_);
- SetError(stream.str());
- return {false, nullptr};
- }
-}
-
-std::unique_ptr<string> UKey2Handshake::GetVerificationString(int byte_length) {
- if (byte_length < 1 || byte_length > 32) {
- SetError("Minimum length is 1 byte, max is 32 bytes.");
- return nullptr;
- }
-
- if (handshake_state_ != InternalState::HANDSHAKE_VERIFICATION_NEEDED) {
- std::ostringstream stream;
- stream << "Unexpected state: " << static_cast<int>(handshake_state_);
- SetError(stream.str());
- return nullptr;
- }
-
- if (!our_key_pair_ || !our_key_pair_->private_key || !their_public_key_) {
- SetError("One of our private key or their public key is null.");
- return nullptr;
- }
-
- switch (handshake_cipher_) {
- case HandshakeCipher::P256_SHA512:
- derived_secret_key_ = CryptoOps::KeyAgreementSha256(
- *(our_key_pair_->private_key), *their_public_key_);
- break;
- default:
- // Unreachable.
- return nullptr;
- }
-
- if (!derived_secret_key_) {
- SetError("Failed to derive shared secret key.");
- return nullptr;
- }
-
- std::unique_ptr<string> auth_string = CryptoOps::Hkdf(
- derived_secret_key_->data().String(),
- string(kUkey2VerificationStringSalt, sizeof(kUkey2VerificationStringSalt)),
- wrapped_client_init_ + wrapped_server_init_);
-
- handshake_state_ = InternalState::HANDSHAKE_VERIFICATION_IN_PROGRESS;
- return auth_string;
-}
-
-bool UKey2Handshake::VerifyHandshake() {
- if (handshake_state_ != InternalState::HANDSHAKE_VERIFICATION_IN_PROGRESS) {
- std::ostringstream stream;
- stream << "Unexpected state: " << static_cast<int>(handshake_state_);
- SetError(stream.str());
- return false;
- }
-
- handshake_state_ = InternalState::HANDSHAKE_FINISHED;
- return true;
-}
-
-std::unique_ptr<D2DConnectionContextV1> UKey2Handshake::ToConnectionContext() {
- if (InternalState::HANDSHAKE_FINISHED != handshake_state_) {
- std::ostringstream stream;
- stream << "ToConnectionContext can only be called when handshake is "
- << "completed, but current state is "
- << static_cast<int>(handshake_state_);
- SetError(stream.str());
- return nullptr;
- }
-
- if (!derived_secret_key_) {
- SetError("Derived key is null.");
- return nullptr;
- }
-
- string info = wrapped_client_init_ + wrapped_server_init_;
- std::unique_ptr<string> master_key_data = CryptoOps::Hkdf(
- derived_secret_key_->data().String(), kUkey2HkdfSalt, info);
-
- if (!master_key_data) {
- SetError("Failed to create master key.");
- return nullptr;
- }
-
- // Derive separate encode keys for both client and server.
- CryptoOps::SecretKey master_key(*master_key_data, CryptoOps::AES_256_KEY);
- std::unique_ptr<CryptoOps::SecretKey> client_key =
- D2DCryptoOps::DeriveNewKeyForPurpose(master_key, "client");
- std::unique_ptr<CryptoOps::SecretKey> server_key =
- D2DCryptoOps::DeriveNewKeyForPurpose(master_key, "server");
- if (!client_key || !server_key) {
- SetError("Failed to derive client or server key.");
- return nullptr;
- }
-
- handshake_state_ = InternalState::HANDSHAKE_ALREADY_USED;
-
- return std::unique_ptr<D2DConnectionContextV1>(new D2DConnectionContextV1(
- handshake_role_ == HandshakeRole::CLIENT ? *client_key : *server_key,
- handshake_role_ == HandshakeRole::CLIENT ? *server_key : *client_key,
- 0 /* initial encode sequence number */,
- 0 /* initial decode sequence number */));
-}
-
-UKey2Handshake::ParseResult UKey2Handshake::ParseClientInitUkey2Message(
- const string& handshake_message) {
- // Deserialize the protobuf.
- Ukey2Message message;
- if (!message.ParseFromString(handshake_message)) {
- return CreateFailedResultWithAlert(Ukey2Alert::BAD_MESSAGE,
- "Can't parse message 1.");
- }
-
- // Verify that message_type == CLIENT_INIT.
- if (!message.has_message_type() ||
- message.message_type() != Ukey2Message::CLIENT_INIT) {
- return CreateFailedResultWithAlert(
- Ukey2Alert::BAD_MESSAGE,
- "Expected, but did not find ClientInit message type.");
- }
-
- // Derserialize message_data as a ClientInit message.
- if (!message.has_message_data()) {
- return CreateFailedResultWithAlert(
- Ukey2Alert::BAD_MESSAGE_DATA,
- "Expected message data, but did not find it.");
- }
-
- Ukey2ClientInit client_init;
- if (!client_init.ParseFromString(message.message_data())) {
- return CreateFailedResultWithAlert(
- Ukey2Alert::BAD_MESSAGE_DATA,
- "Can't parse message data into ClientInit.");
- }
-
- // Check that version == VERSION.
- if (!client_init.has_version()) {
- return CreateFailedResultWithAlert(Ukey2Alert::BAD_VERSION,
- "ClientInit missing version.");
- }
- if (client_init.version() != kVersion) {
- return CreateFailedResultWithAlert(Ukey2Alert::BAD_VERSION,
- "ClientInit version mismatch.");
- }
-
- // Check that random is exactly kNonceLengthInBytes.
- if (!client_init.has_random()) {
- return CreateFailedResultWithAlert(Ukey2Alert::BAD_RANDOM,
- "ClientInit missing random.");
- }
- if (client_init.random().length() != kNonceLengthInBytes) {
- return CreateFailedResultWithAlert(
- Ukey2Alert::BAD_RANDOM, "ClientInit has incorrect nonce length.");
- }
-
- // Check to see if any of the handshake_cipher in handshake_cipher_commitment
- // are acceptable. Servers should select the first ahdnshake_cipher that it
- // finds acceptable to support clients signalling deprecated but supported
- // HandshakeCiphers. If no handshake_cipher is acceptable (or there are no
- // HandshakeCiphers in the message), the server sends a BAD_HANDSHAKE_CIPHER
- // alert message.
- if (client_init.cipher_commitments_size() == 0) {
- return CreateFailedResultWithAlert(
- Ukey2Alert::BAD_HANDSHAKE_CIPHER,
- "ClientInit is missing cipher commitments.");
- }
-
- for (const Ukey2ClientInit::CipherCommitment& commitment :
- client_init.cipher_commitments()) {
- if (!commitment.has_handshake_cipher() || !commitment.has_commitment() ||
- commitment.commitment().empty()) {
- return CreateFailedResultWithAlert(
- Ukey2Alert::BAD_HANDSHAKE_CIPHER,
- "ClientInit has improperly formatted cipher commitment.");
- }
-
- // TODO(aczeskis): for now we only support one cipher, eventually support
- // more.
- if (commitment.handshake_cipher() == static_cast<int>(handshake_cipher_)) {
- peer_commitment_ = commitment.commitment();
- }
- }
-
- if (peer_commitment_.empty()) {
- return CreateFailedResultWithAlert(Ukey2Alert::BAD_HANDSHAKE_CIPHER,
- "No acceptable commitments found");
- }
-
- // Checks that next_protocol contains a protocol that the server supports. We
- // currently only support one protocol.
- if (!client_init.has_next_protocol() ||
- client_init.next_protocol() != kNextProtocol) {
- return CreateFailedResultWithAlert(Ukey2Alert::BAD_NEXT_PROTOCOL,
- "Incorrect next protocol.");
- }
-
- // Store raw message for AUTH_STRING computation.
- wrapped_client_init_ = handshake_message;
- handshake_state_ = InternalState::SERVER_AFTER_CLIENT_INIT;
- return CreateSuccessResult();
-}
-
-UKey2Handshake::ParseResult UKey2Handshake::ParseServerInitUkey2Message(
- const string& handshake_message) {
- // Deserialize the protobuf.
- Ukey2Message message;
- if (!message.ParseFromString(handshake_message)) {
- return CreateFailedResultWithAlert(Ukey2Alert::BAD_MESSAGE,
- "Can't parse message 2.");
- }
-
- // Verify that message_type == SERVER_INIT.
- if (!message.has_message_type() ||
- message.message_type() != Ukey2Message::SERVER_INIT) {
- return CreateFailedResultWithAlert(
- Ukey2Alert::BAD_MESSAGE,
- "Expected, but did not find SERVER_INIT message type.");
- }
-
- // Derserialize message_data as a ServerInit message.
- if (!message.has_message_data()) {
- return CreateFailedResultWithAlert(
- Ukey2Alert::BAD_MESSAGE_DATA,
- "Expected message data, but did not find it.");
- }
-
- Ukey2ServerInit server_init;
- if (!server_init.ParseFromString(message.message_data())) {
- return CreateFailedResultWithAlert(
- Ukey2Alert::BAD_MESSAGE_DATA,
- "Can't parse message data into ServerInit.");
- }
-
- // Check that version == VERSION.
- if (!server_init.has_version()) {
- return CreateFailedResultWithAlert(Ukey2Alert::BAD_VERSION,
- "ServerInit missing version.");
- }
- if (server_init.version() != kVersion) {
- return CreateFailedResultWithAlert(Ukey2Alert::BAD_VERSION,
- "ServerInit version mismatch.");
- }
-
- // Check that random is exactly kNonceLengthInBytes.
- if (!server_init.has_random()) {
- return CreateFailedResultWithAlert(Ukey2Alert::BAD_RANDOM,
- "ServerInit missing random.");
- }
- if (server_init.random().length() != kNonceLengthInBytes) {
- return CreateFailedResultWithAlert(
- Ukey2Alert::BAD_RANDOM, "ServerInit has incorrect nonce length.");
- }
-
- // Check that the handshake_cipher matches a handshake cipher that was sent in
- // ClientInit::cipher_commitments().
- if (!server_init.has_handshake_cipher()) {
- return CreateFailedResultWithAlert(Ukey2Alert::BAD_HANDSHAKE_CIPHER,
- "No handshake cipher found.");
- }
-
- Ukey2HandshakeCipher cipher = server_init.handshake_cipher();
- HandshakeCipher server_cipher;
- switch (static_cast<HandshakeCipher>(cipher)) {
- case HandshakeCipher::P256_SHA512:
- server_cipher = static_cast<HandshakeCipher>(cipher);
- break;
- default:
- return CreateFailedResultWithAlert(Ukey2Alert::BAD_HANDSHAKE_CIPHER,
- "No acceptable handshake found.");
- }
-
- // Check that public_key parses into a correct public key structure.
- if (!server_init.has_public_key()) {
- return CreateFailedResultWithAlert(Ukey2Alert::BAD_PUBLIC_KEY,
- "No public key found in ServerInit.");
- }
-
- their_public_key_ = ParsePublicKey(server_init.public_key());
- if (!their_public_key_) {
- return CreateFailedResultWithAlert(Ukey2Alert::BAD_PUBLIC_KEY,
- "Failed to parse public key.");
- }
-
- // Store raw message for AUTH_STRING computation.
- wrapped_server_init_ = handshake_message;
- handshake_state_ = InternalState::CLIENT_AFTER_SERVER_INIT;
- return CreateSuccessResult();
-}
-
-UKey2Handshake::ParseResult UKey2Handshake::ParseClientFinishUkey2Message(
- const string& handshake_message) {
- // Deserialize the protobuf.
- Ukey2Message message;
- if (!message.ParseFromString(handshake_message)) {
- return CreateFailedResultWithoutAlert("Can't parse message 3.");
- }
-
- // Verify that message_type == CLIENT_FINISH.
- if (!message.has_message_type() ||
- message.message_type() != Ukey2Message::CLIENT_FINISH) {
- return CreateFailedResultWithoutAlert(
- "Expected, but did not find CLIENT_FINISH message type.");
- }
-
- // Verify that the hash of the CLientFinished message matches the expected
- // commitment from ClientInit.
- if (!VerifyCommitment(handshake_message)) {
- return CreateFailedResultWithoutAlert(last_error_);
- }
-
- // Deserialize message_data as a ClientFinished message.
- if (!message.has_message_data()) {
- return CreateFailedResultWithoutAlert(
- "Expected message data, but didn't find it.");
- }
-
- Ukey2ClientFinished client_finished;
- if (!client_finished.ParseFromString(message.message_data())) {
- return CreateFailedResultWithoutAlert("Failed to parse ClientFinished.");
- }
-
- // Check that public_key parses into a correct public key structure.
- if (!client_finished.has_public_key()) {
- return CreateFailedResultWithoutAlert(
- "No public key found in ClientFinished.");
- }
-
- their_public_key_ = ParsePublicKey(client_finished.public_key());
- if (!their_public_key_) {
- return CreateFailedResultWithoutAlert("Failed to parse public key.");
- }
-
- handshake_state_ = InternalState::HANDSHAKE_VERIFICATION_NEEDED;
- return CreateSuccessResult();
-}
-
-UKey2Handshake::ParseResult UKey2Handshake::CreateFailedResultWithAlert(
- Ukey2Alert::AlertType alert_type, const string& error_message) {
- if (!Ukey2Alert_AlertType_IsValid(alert_type)) {
- std::ostringstream stream;
- stream << "Unknown alert type: " << static_cast<int>(alert_type);
- SetError(stream.str());
- return {false, nullptr};
- }
-
- Ukey2Alert alert;
- alert.set_type(alert_type);
- if (!error_message.empty()) {
- alert.set_error_message(error_message);
- }
-
- std::unique_ptr<string> alert_message =
- MakeUkey2Message(Ukey2Message::ALERT, alert.SerializeAsString());
-
- SetError(error_message);
- ParseResult result{false, std::move(alert_message)};
- return result;
-}
-
-UKey2Handshake::ParseResult
-UKey2Handshake::CreateFailedResultWithoutAlert(const string& error_message) {
- SetError(error_message);
- return {false, nullptr};
-}
-
-UKey2Handshake::ParseResult UKey2Handshake::CreateSuccessResult() {
- return {true, nullptr};
-}
-
-bool UKey2Handshake::VerifyCommitment(const string& handshake_message) {
- std::unique_ptr<ByteBuffer> actual_client_finish_hash;
- switch (handshake_cipher_) {
- case HandshakeCipher::P256_SHA512:
- actual_client_finish_hash =
- CryptoOps::Sha512(ByteBuffer(handshake_message));
- break;
- default:
- // Unreachable.
- return false;
- }
-
- if (!actual_client_finish_hash) {
- SetError("Failed to hash ClientFinish message.");
- return false;
- }
-
- // Note: Equals() is a time constant comparison operation.
- if (!actual_client_finish_hash->Equals(peer_commitment_)) {
- SetError("Failed to verify commitment.");
- return false;
- }
-
- return true;
-}
-
-std::unique_ptr<Ukey2ClientInit::CipherCommitment>
-UKey2Handshake::GenerateP256Sha512Commitment() {
- // Generate the corresponding ClientFinished message if it's not done yet.
- if (raw_message3_map_.count(HandshakeCipher::P256_SHA512) == 0) {
- if (!our_key_pair_ || !our_key_pair_->public_key) {
- SetError("Invalid public key.");
- return nullptr;
- }
-
- std::unique_ptr<GenericPublicKey> generic_public_key =
- PublicKeyProtoUtil::EncodePublicKey(*(our_key_pair_->public_key));
- if (!generic_public_key) {
- SetError("Failed to encode generic public key.");
- return nullptr;
- }
-
- Ukey2ClientFinished client_finished;
- client_finished.set_public_key(generic_public_key->SerializeAsString());
- std::unique_ptr<string> serialized_ukey2_message = MakeUkey2Message(
- Ukey2Message::CLIENT_FINISH, client_finished.SerializeAsString());
- if (!serialized_ukey2_message) {
- SetError("Failed to serialized Ukey2Message.");
- return nullptr;
- }
-
- raw_message3_map_[HandshakeCipher::P256_SHA512] = *serialized_ukey2_message;
- }
-
- // Create the SHA512 commitment from raw message 3.
- std::unique_ptr<ByteBuffer> commitment = CryptoOps::Sha512(
- ByteBuffer(raw_message3_map_[HandshakeCipher::P256_SHA512]));
- if (!commitment) {
- SetError("Failed to hash message for commitment.");
- return nullptr;
- }
-
- // Wrap the commitment in a proto.
- std::unique_ptr<Ukey2ClientInit::CipherCommitment>
- handshake_cipher_commitment(new Ukey2ClientInit::CipherCommitment());
- handshake_cipher_commitment->set_handshake_cipher(P256_SHA512);
- handshake_cipher_commitment->set_commitment(commitment->String());
-
- return handshake_cipher_commitment;
-}
-
-std::unique_ptr<string> UKey2Handshake::MakeClientInitUkey2Message() {
- std::unique_ptr<ByteBuffer> nonce =
- CryptoOps::SecureRandom(kNonceLengthInBytes);
- if (!nonce) {
- SetError("Failed to generate nonce.");
- return nullptr;
- }
-
- Ukey2ClientInit client_init;
- client_init.set_version(kVersion);
- client_init.set_random(nonce->String());
- client_init.set_next_protocol(kNextProtocol);
-
- // At the moment, we only support one cipher.
- std::unique_ptr<Ukey2ClientInit::CipherCommitment>
- handshake_cipher_commitment = GenerateP256Sha512Commitment();
- if (!handshake_cipher_commitment) {
- // |last_error_| already set.
- return nullptr;
- }
- *(client_init.add_cipher_commitments()) = *handshake_cipher_commitment;
-
- return MakeUkey2Message(Ukey2Message::CLIENT_INIT,
- client_init.SerializeAsString());
-}
-
-std::unique_ptr<string> UKey2Handshake::MakeServerInitUkey2Message() {
- std::unique_ptr<ByteBuffer> nonce =
- CryptoOps::SecureRandom(kNonceLengthInBytes);
- if (!nonce) {
- SetError("Failed to generate nonce.");
- return nullptr;
- }
-
- if (!our_key_pair_ || !our_key_pair_->public_key) {
- SetError("Invalid key pair.");
- return nullptr;
- }
-
- std::unique_ptr<GenericPublicKey> public_key =
- PublicKeyProtoUtil::EncodePublicKey(*(our_key_pair_->public_key));
- if (!public_key) {
- SetError("Failed to encode public key.");
- return nullptr;
- }
-
- Ukey2ServerInit server_init;
- server_init.set_version(kVersion);
- server_init.set_random(nonce->String());
- server_init.set_handshake_cipher(
- static_cast<Ukey2HandshakeCipher>(handshake_cipher_));
- server_init.set_public_key(public_key->SerializeAsString());
-
- return MakeUkey2Message(Ukey2Message::SERVER_INIT,
- server_init.SerializeAsString());
-}
-
-// Generates the serialized representation of a Ukey2Message based on the
-// provided |type| and |data|. On error, returns nullptr and writes error
-// message to |out_error|.
-std::unique_ptr<string> UKey2Handshake::MakeUkey2Message(
- Ukey2Message::Type type, const string& data) {
- Ukey2Message message;
- if (!Ukey2Message::Type_IsValid(type)) {
- std::ostringstream stream;
- stream << "Invalid message type: " << type;
- SetError(stream.str());
- return nullptr;
- }
- message.set_message_type(type);
-
- // Only ALERT messages can have a blank data field.
- if (type != Ukey2Message::ALERT) {
- if (data.length() == 0) {
- SetError("Cannot send empty message data for non-alert messages");
- return nullptr;
- }
- }
- message.set_message_data(data);
-
- std::unique_ptr<string> serialized(new string());
- message.SerializeToString(serialized.get());
- return serialized;
-}
-
-void UKey2Handshake::SetError(const string& error_message) {
- handshake_state_ = InternalState::HANDSHAKE_ERROR;
- last_error_ = error_message;
-}
-
-} // namespace securegcm
diff --git a/src/main/cpp/src/securegcm/ukey2_shell.cc b/src/main/cpp/src/securegcm/ukey2_shell.cc
deleted file mode 100644
index 99a35a8..0000000
--- a/src/main/cpp/src/securegcm/ukey2_shell.cc
+++ /dev/null
@@ -1,297 +0,0 @@
-// Copyright 2020 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
-//
-// https://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.
-
-// The ukey2_shell binary is a command-line based wrapper, exercising the
-// UKey2Handshake class. Its main use is to be run in a Java test, testing the
-// compatibility of the Java and C++ implementations.
-//
-// This program can be run in two modes, initiator or responder (default is
-// initiator):
-// ukey2_shell --mode=initiator --verification_string_length=32
-// ukey2_shell --mode=responder --verification_string_length=32
-//
-// In initiator mode, the program performs the initiator handshake, and in
-// responder mode, it performs the responder handshake.
-//
-// After the handshake is done, the program establishes a secure connection and
-// enters a loop in which it processes the following commands:
-// * encrypt <payload>: encrypts the payload and prints it.
-// * decrypt <message>: decrypts the message and prints the payload.
-// * session_unique: prints the session unique value.
-//
-// IO is performed on stdin and stdout. To provide frame control, all frames
-// will have the following simple format:
-// [ length | bytes ]
-// where |length| is a 4 byte big-endian encoded unsigned integer.
-#include <cassert>
-#include <cstdio>
-#include <iostream>
-#include <memory>
-
-#include "securegcm/ukey2_handshake.h"
-#include "absl/container/fixed_array.h"
-#include "absl/flags/flag.h"
-#include "absl/flags/parse.h"
-
-#define LOG(ERROR) std::cerr
-#define CHECK_EQ(a, b) do { if ((a) != (b)) abort(); } while(0)
-
-ABSL_FLAG(
- int, verification_string_length, 32,
- "The length in bytes of the verification string. Must be a value between 1"
- "and 32.");
-ABSL_FLAG(string, mode, "initiator",
- "The mode to run as: one of [initiator, responder]");
-
-namespace securegcm {
-
-namespace {
-
-// Writes |message| to stdout in the frame format.
-void WriteFrame(const string& message) {
- // Write length of |message| in little-endian.
- const uint32_t length = message.length();
- fputc((length >> (3 * 8)) & 0xFF, stdout);
- fputc((length >> (2 * 8)) & 0xFF, stdout);
- fputc((length >> (1 * 8)) & 0xFF, stdout);
- fputc((length >> (0 * 8)) & 0xFF, stdout);
-
- // Write message to stdout.
- CHECK_EQ(message.length(),
- fwrite(message.c_str(), 1, message.length(), stdout));
- CHECK_EQ(0, fflush(stdout));
-}
-
-// Returns a message read from stdin after parsing it from the frame format.
-string ReadFrame() {
- // Read length of the frame from the stream.
- uint8_t length_data[sizeof(uint32_t)];
- CHECK_EQ(sizeof(uint32_t), fread(&length_data, 1, sizeof(uint32_t), stdin));
-
- uint32_t length = 0;
- length |= static_cast<uint32_t>(length_data[0]) << (3 * 8);
- length |= static_cast<uint32_t>(length_data[1]) << (2 * 8);
- length |= static_cast<uint32_t>(length_data[2]) << (1 * 8);
- length |= static_cast<uint32_t>(length_data[3]) << (0 * 8);
-
- // Read |length| bytes from the stream.
- absl::FixedArray<char> buffer(length);
- CHECK_EQ(length, fread(buffer.data(), 1, length, stdin));
-
- return string(buffer.data(), length);
-}
-
-} // namespace
-
-// Handles the runtime of the program in initiator or responder mode.
-class UKey2Shell {
- public:
- explicit UKey2Shell(int verification_string_length);
- ~UKey2Shell();
-
- // Runs the shell, performing the initiator handshake for authentication.
- bool RunAsInitiator();
-
- // Runs the shell, performing the responder handshake for authentication.
- bool RunAsResponder();
-
- private:
- // Writes the next handshake message obtained from |ukey2_handshake_| to
- // stdout.
- // If an error occurs, |tag| is logged.
- bool WriteNextHandshakeMessage(const string& tag);
-
- // Reads the next handshake message from stdin and parses it using
- // |ukey2_handshake_|.
- // If an error occurs, |tag| is logged.
- bool ReadNextHandshakeMessage(const string& tag);
-
- // Writes the verification string to stdout and waits for a confirmation from
- // stdin.
- bool ConfirmVerificationString();
-
- // After authentication is completed, this function runs the loop handing the
- // secure connection.
- bool RunSecureConnectionLoop();
-
- std::unique_ptr<UKey2Handshake> ukey2_handshake_;
- const int verification_string_length_;
-};
-
-UKey2Shell::UKey2Shell(int verification_string_length)
- : verification_string_length_(verification_string_length) {}
-
-UKey2Shell::~UKey2Shell() {}
-
-bool UKey2Shell::WriteNextHandshakeMessage(const string& tag) {
- const std::unique_ptr<string> message =
- ukey2_handshake_->GetNextHandshakeMessage();
- if (!message) {
- LOG(ERROR) << "Failed to create [" << tag
- << "] message: " << ukey2_handshake_->GetLastError();
- return false;
- }
- WriteFrame(*message);
- return true;
-}
-
-bool UKey2Shell::ReadNextHandshakeMessage(const string& tag) {
- const string message = ReadFrame();
- const UKey2Handshake::ParseResult result =
- ukey2_handshake_->ParseHandshakeMessage(message);
- if (!result.success) {
- LOG(ERROR) << "Failed to parse [" << tag
- << "] message: " << ukey2_handshake_->GetLastError();
- if (result.alert_to_send) {
- WriteFrame(*result.alert_to_send);
- }
- return false;
- }
- return true;
-}
-
-bool UKey2Shell::ConfirmVerificationString() {
- const std::unique_ptr<string> auth_string =
- ukey2_handshake_->GetVerificationString(verification_string_length_);
- if (!auth_string) {
- LOG(ERROR) << "Failed to get verification string: "
- << ukey2_handshake_->GetLastError();
- return false;
- }
- WriteFrame(*auth_string);
-
- // Wait for ack message.
- const string message = ReadFrame();
- if (message != "ok") {
- LOG(ERROR) << "Expected string 'ok'";
- return false;
- }
- ukey2_handshake_->VerifyHandshake();
- return true;
-}
-
-bool UKey2Shell::RunSecureConnectionLoop() {
- const std::unique_ptr<D2DConnectionContextV1> connection_context =
- ukey2_handshake_->ToConnectionContext();
- if (!connection_context) {
- LOG(ERROR) << "Failed to create connection context: "
- << ukey2_handshake_->GetLastError();
- return false;
- }
-
- for (;;) {
- // Parse the next expression.
- const string expression = ReadFrame();
- const size_t pos = expression.find(" ");
- if (pos == std::string::npos) {
- LOG(ERROR) << "Invalid command in connection loop.";
- return false;
- }
- const string command = expression.substr(0, pos);
-
- if (command == "encrypt") {
- const string payload = expression.substr(pos + 1, expression.length());
- std::unique_ptr<string> encoded_message =
- connection_context->EncodeMessageToPeer(payload);
- if (!encoded_message) {
- LOG(ERROR) << "Failed to encode payload of size " << payload.length();
- return false;
- }
- WriteFrame(*encoded_message);
- } else if (command == "decrypt") {
- const string message = expression.substr(pos + 1, expression.length());
- std::unique_ptr<string> decoded_payload =
- connection_context->DecodeMessageFromPeer(message);
- if (!decoded_payload) {
- LOG(ERROR) << "Failed to decode message of size " << message.length();
- return false;
- }
- WriteFrame(*decoded_payload);
- } else if (command == "session_unique") {
- std::unique_ptr<string> session_unique =
- connection_context->GetSessionUnique();
- if (!session_unique) {
- LOG(ERROR) << "Failed to get session unique.";
- return false;
- }
- WriteFrame(*session_unique);
- } else {
- LOG(ERROR) << "Unrecognized command: " << command;
- return false;
- }
- }
-}
-
-bool UKey2Shell::RunAsInitiator() {
- ukey2_handshake_ = UKey2Handshake::ForInitiator(
- UKey2Handshake::HandshakeCipher::P256_SHA512);
- if (!ukey2_handshake_) {
- LOG(ERROR) << "Unable to create UKey2Handshake";
- return false;
- }
-
- // Perform handshake.
- if (!WriteNextHandshakeMessage("Initiator Init")) return false;
- if (!ReadNextHandshakeMessage("Responder Init")) return false;
- if (!WriteNextHandshakeMessage("Initiator Finish")) return false;
- if (!ConfirmVerificationString()) return false;
-
- // Create a connection context.
- return RunSecureConnectionLoop();
-}
-
-bool UKey2Shell::RunAsResponder() {
- ukey2_handshake_ = UKey2Handshake::ForResponder(
- UKey2Handshake::HandshakeCipher::P256_SHA512);
- if (!ukey2_handshake_) {
- LOG(ERROR) << "Unable to create UKey2Handshake";
- return false;
- }
-
- // Perform handshake.
- if (!ReadNextHandshakeMessage("Initiator Init")) return false;
- if (!WriteNextHandshakeMessage("Responder Init")) return false;
- if (!ReadNextHandshakeMessage("Initiator Finish")) return false;
- if (!ConfirmVerificationString()) return false;
-
- // Create a connection context.
- return RunSecureConnectionLoop();
-}
-
-} // namespace securegcm
-
-int main(int argc, char** argv) {
- absl::ParseCommandLine(argc, argv);
-
- const int verification_string_length =
- absl::GetFlag(FLAGS_verification_string_length);
- if (verification_string_length < 1 || verification_string_length > 32) {
- LOG(ERROR) << "Invalid flag value, verification_string_length: "
- << verification_string_length;
- return 1;
- }
-
- securegcm::UKey2Shell shell(verification_string_length);
- int exit_code = 0;
- const string mode = absl::GetFlag(FLAGS_mode);
- if (mode == "initiator") {
- exit_code = !shell.RunAsInitiator();
- } else if (mode == "responder") {
- exit_code = !shell.RunAsResponder();
- } else {
- LOG(ERROR) << "Invalid flag value, mode: " << mode;
- exit_code = 1;
- }
- return exit_code;
-}
diff --git a/src/main/cpp/test/securegcm/CMakeLists.txt b/src/main/cpp/test/securegcm/CMakeLists.txt
deleted file mode 100644
index 272f919..0000000
--- a/src/main/cpp/test/securegcm/CMakeLists.txt
+++ /dev/null
@@ -1,31 +0,0 @@
-# Copyright 2020 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
-#
-# https://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.
-
-add_executable(ukey2_test
- d2d_connection_context_v1_test.cc
- d2d_crypto_ops_test.cc
- java_util_test.cc
-)
-
-target_link_libraries(ukey2_test
- PUBLIC
- ukey2
- gtest
- gtest_main
-)
-
-add_test(
- NAME ukey2_test
- COMMAND ukey2_test
-)
diff --git a/src/main/cpp/test/securegcm/d2d_connection_context_v1_test.cc b/src/main/cpp/test/securegcm/d2d_connection_context_v1_test.cc
deleted file mode 100644
index daf69d1..0000000
--- a/src/main/cpp/test/securegcm/d2d_connection_context_v1_test.cc
+++ /dev/null
@@ -1,124 +0,0 @@
-// Copyright 2020 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
-//
-// https://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 "securegcm/d2d_connection_context_v1.h"
-
-#include "securegcm/d2d_crypto_ops.h"
-#include "gtest/gtest.h"
-
-namespace securegcm {
-
-using securemessage::CryptoOps;
-
-namespace {
-
-// The encode and decode keys should be 32 bytes.
-const char kEncodeKeyData[] = "initiator_encode_key_for_aes_256";
-const char kDecodeKeyData[] = "initiator_decode_key_for_aes_256";
-
-} // namespace
-
-// A friend to access the private variables of D2DConnectionContextV1.
-class D2DConnectionContextV1Peer {
- public:
- explicit D2DConnectionContextV1Peer(const std::string& savedSessionInfo) {
- context_ = D2DConnectionContextV1::FromSavedSession(savedSessionInfo);
- }
-
- D2DConnectionContextV1* GetContext() { return context_.get(); }
-
- uint32_t GetEncodeSequenceNumber() {
- return context_->encode_sequence_number_;
- }
-
- uint32_t GetDecodeSequenceNumber() {
- return context_->decode_sequence_number_;
- }
-
- private:
- std::unique_ptr<D2DConnectionContextV1> context_;
-};
-
-TEST(D2DConnectionContextionV1Test, SaveSession) {
- CryptoOps::SecretKey encodeKey = CryptoOps::SecretKey(
- kEncodeKeyData, CryptoOps::KeyAlgorithm::AES_256_KEY);
- CryptoOps::SecretKey decodeKey = CryptoOps::SecretKey(
- kDecodeKeyData, CryptoOps::KeyAlgorithm::AES_256_KEY);
-
- D2DConnectionContextV1 initiator =
- D2DConnectionContextV1(encodeKey, decodeKey, 0, 1);
- D2DConnectionContextV1 responder =
- D2DConnectionContextV1(decodeKey, encodeKey, 1, 0);
-
- std::unique_ptr<std::string> initiatorSavedSessionState =
- initiator.SaveSession();
- std::unique_ptr<std::string> responderSavedSessionState =
- responder.SaveSession();
-
- D2DConnectionContextV1Peer restoredInitiator =
- D2DConnectionContextV1Peer(*initiatorSavedSessionState);
- D2DConnectionContextV1Peer restoredResponder =
- D2DConnectionContextV1Peer(*responderSavedSessionState);
-
- // Verify internal state matches initialization.
- EXPECT_EQ(0, restoredInitiator.GetEncodeSequenceNumber());
- EXPECT_EQ(1, restoredInitiator.GetDecodeSequenceNumber());
- EXPECT_EQ(1, restoredResponder.GetEncodeSequenceNumber());
- EXPECT_EQ(0, restoredResponder.GetDecodeSequenceNumber());
-
- EXPECT_EQ(*restoredInitiator.GetContext()->GetSessionUnique(),
- *restoredResponder.GetContext()->GetSessionUnique());
-
- const std::string message = "ping";
-
- // Ensure that they can still talk to one another.
- std::string encodedMessage =
- *restoredInitiator.GetContext()->EncodeMessageToPeer(message);
- std::string decodedMessage =
- *restoredResponder.GetContext()->DecodeMessageFromPeer(encodedMessage);
-
- EXPECT_EQ(message, decodedMessage);
-
- encodedMessage =
- *restoredResponder.GetContext()->EncodeMessageToPeer(message);
- decodedMessage =
- *restoredInitiator.GetContext()->DecodeMessageFromPeer(encodedMessage);
-
- EXPECT_EQ(message, decodedMessage);
-}
-
-TEST(D2DConnectionContextionV1Test, SaveSession_TooShort) {
- CryptoOps::SecretKey encodeKey = CryptoOps::SecretKey(
- kEncodeKeyData, CryptoOps::KeyAlgorithm::AES_256_KEY);
- CryptoOps::SecretKey decodeKey = CryptoOps::SecretKey(
- kDecodeKeyData, CryptoOps::KeyAlgorithm::AES_256_KEY);
-
- D2DConnectionContextV1 initiator =
- D2DConnectionContextV1(encodeKey, decodeKey, 0, 1);
-
- std::unique_ptr<std::string> initiatorSavedSessionState =
- initiator.SaveSession();
-
- // Try to rebuild the context with a shorter session state.
- std::string shortSessionState = initiatorSavedSessionState->substr(
- 0, initiatorSavedSessionState->size() - 1);
-
- D2DConnectionContextV1Peer restoredInitiator =
- D2DConnectionContextV1Peer(shortSessionState);
-
- // nullptr is returned on error. It should not crash.
- EXPECT_EQ(restoredInitiator.GetContext(), nullptr);
-}
-
-} // namespace securegcm
diff --git a/src/main/cpp/test/securegcm/d2d_crypto_ops_test.cc b/src/main/cpp/test/securegcm/d2d_crypto_ops_test.cc
deleted file mode 100644
index 5acbb89..0000000
--- a/src/main/cpp/test/securegcm/d2d_crypto_ops_test.cc
+++ /dev/null
@@ -1,158 +0,0 @@
-// Copyright 2020 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
-//
-// https://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 "securegcm/d2d_crypto_ops.h"
-
-#include "gtest/gtest.h"
-#include "securemessage/crypto_ops.h"
-#include "securemessage/secure_message_builder.h"
-
-namespace securegcm {
-
-using securemessage::CryptoOps;
-using securemessage::HeaderAndBody;
-using securemessage::SecureMessage;
-
-namespace {
-
-const char kPayloadData[] = "Test payload";
-const char kSecretKeyData[] = "secret key must be 32 bytes long";
-const char kOtherSecretKeyData[] = "other secret key****************";
-const char kInvalidSigncryptedMessage[] = "Not a protobuf";
-const int kSupportedProtocolVersion = 1;
-const int kUnsupportedProtocolVersion = 2;
-
-} // namespace
-
-class D2DCryptoOpsTest : public testing::Test {
- public:
- D2DCryptoOpsTest()
- : payload_(DEVICE_TO_DEVICE_MESSAGE, kPayloadData),
- secret_key_(kSecretKeyData, CryptoOps::AES_256_KEY) {}
-
- protected:
- const D2DCryptoOps::Payload payload_;
- const CryptoOps::SecretKey secret_key_;
-};
-
-TEST_F(D2DCryptoOpsTest, Signcrypt_EmptyPayload) {
- // Signcrypting an empty payload should fail.
- D2DCryptoOps::Payload empty_payload(DEVICE_TO_DEVICE_MESSAGE, string());
- EXPECT_FALSE(D2DCryptoOps::SigncryptPayload(empty_payload, secret_key_));
-}
-
-TEST_F(D2DCryptoOpsTest, VerifyDecrypt_InvalidMessage) {
- // VerifyDecrypting an invalid payload should fail.
- EXPECT_FALSE(D2DCryptoOps::VerifyDecryptPayload(kInvalidSigncryptedMessage,
- secret_key_));
-}
-
-TEST_F(D2DCryptoOpsTest, VerifyDecrypt_NoPublicMetadata) {
- std::unique_ptr<string> signcrypted_message =
- D2DCryptoOps::SigncryptPayload(payload_, secret_key_);
-
- // Clear metadata field in header.
- SecureMessage secure_message;
- ASSERT_TRUE(secure_message.ParseFromString(*signcrypted_message));
- HeaderAndBody header_and_body;
- ASSERT_TRUE(
- header_and_body.ParseFromString(secure_message.header_and_body()));
- header_and_body.mutable_header()->clear_public_metadata();
- secure_message.set_header_and_body(header_and_body.SerializeAsString());
-
- // Decrypting the message should now fail.
- EXPECT_FALSE(D2DCryptoOps::VerifyDecryptPayload(
- secure_message.SerializeAsString(), secret_key_));
-}
-
-TEST_F(D2DCryptoOpsTest, VerifyDecrypt_ProtocolVersionNotSupported) {
- std::unique_ptr<string> signcrypted_message =
- D2DCryptoOps::SigncryptPayload(payload_, secret_key_);
-
- // Change version in metadata field in header to an unsupported version.
- SecureMessage secure_message;
- ASSERT_TRUE(secure_message.ParseFromString(*signcrypted_message));
- HeaderAndBody header_and_body;
- ASSERT_TRUE(
- header_and_body.ParseFromString(secure_message.header_and_body()));
- GcmMetadata metadata;
- ASSERT_TRUE(
- metadata.ParseFromString(header_and_body.header().public_metadata()));
- EXPECT_EQ(kSupportedProtocolVersion, metadata.version());
- metadata.set_version(kUnsupportedProtocolVersion);
- header_and_body.mutable_header()->set_public_metadata(
- metadata.SerializeAsString());
- secure_message.set_header_and_body(header_and_body.SerializeAsString());
-
- // Decrypting the message should now fail.
- EXPECT_FALSE(D2DCryptoOps::VerifyDecryptPayload(
- secure_message.SerializeAsString(), secret_key_));
-}
-
-TEST_F(D2DCryptoOpsTest, SigncryptThenVerifyDecrypt_SuccessWithSameKey) {
- // Signcrypt the payload.
- std::unique_ptr<string> signcrypted_message =
- D2DCryptoOps::SigncryptPayload(payload_, secret_key_);
- ASSERT_TRUE(signcrypted_message);
-
- // Decrypt the signcrypted message.
- std::unique_ptr<D2DCryptoOps::Payload> decrypted_payload =
- D2DCryptoOps::VerifyDecryptPayload(*signcrypted_message, secret_key_);
- ASSERT_TRUE(decrypted_payload);
-
- // Check that decrypted payload is the same.
- EXPECT_EQ(payload_.type(), decrypted_payload->type());
- EXPECT_EQ(payload_.message(), decrypted_payload->message());
-}
-
-TEST_F(D2DCryptoOpsTest, SigncryptThenVerifyDecrypt_FailsWithDifferentKey) {
- CryptoOps::SecretKey other_secret_key(kOtherSecretKeyData,
- CryptoOps::AES_256_KEY);
-
- // Signcrypt the payload with first secret key.
- std::unique_ptr<string> signcrypted_message =
- D2DCryptoOps::SigncryptPayload(payload_, secret_key_);
- ASSERT_TRUE(signcrypted_message);
-
- // Decrypting the signcrypted message with the other secret key should fail.
- EXPECT_FALSE(D2DCryptoOps::VerifyDecryptPayload(*signcrypted_message,
- other_secret_key));
-}
-
-TEST_F(D2DCryptoOpsTest, DeriveNewKeyForPurpose_ClientServer) {
- CryptoOps::SecretKey master_key(kSecretKeyData, CryptoOps::AES_256_KEY);
-
- std::unique_ptr<CryptoOps::SecretKey> derived_key1 =
- D2DCryptoOps::DeriveNewKeyForPurpose(master_key, "client");
- std::unique_ptr<CryptoOps::SecretKey> derived_key2 =
- D2DCryptoOps::DeriveNewKeyForPurpose(master_key, "server");
-
- ASSERT_TRUE(derived_key1);
- ASSERT_TRUE(derived_key2);
- EXPECT_EQ(CryptoOps::AES_256_KEY, derived_key1->algorithm());
- EXPECT_EQ(CryptoOps::AES_256_KEY, derived_key2->algorithm());
- EXPECT_NE(derived_key1->data().String(), derived_key2->data().String());
-}
-
-TEST_F(D2DCryptoOpsTest, DeriveNewKeyForPurpose_InvalidMasterKeySize) {
- CryptoOps::SecretKey master_key("Invalid Size", CryptoOps::AES_256_KEY);
- EXPECT_FALSE(D2DCryptoOps::DeriveNewKeyForPurpose(master_key, "purpose"));
-}
-
-TEST_F(D2DCryptoOpsTest, DeriveNewKeyForPurpose_PurposeEmpty) {
- CryptoOps::SecretKey master_key(kSecretKeyData, CryptoOps::AES_256_KEY);
- EXPECT_FALSE(D2DCryptoOps::DeriveNewKeyForPurpose(master_key, string()));
-}
-
-} // namespace securegcm
diff --git a/src/main/cpp/test/securegcm/java_util_test.cc b/src/main/cpp/test/securegcm/java_util_test.cc
deleted file mode 100644
index 20928fa..0000000
--- a/src/main/cpp/test/securegcm/java_util_test.cc
+++ /dev/null
@@ -1,84 +0,0 @@
-// Copyright 2020 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
-//
-// https://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 "securegcm/java_util.h"
-
-#include <limits>
-
-#include "gtest/gtest.h"
-
-namespace securegcm {
-
-using securemessage::ByteBuffer;
-
-namespace {
-
-int32_t kMinInt32 = std::numeric_limits<int32_t>::min();
-int32_t kMaxInt32 = std::numeric_limits<int32_t>::max();
-
-} // namespace
-
-
-TEST(JavaUtilTest, TestJavaAdd_InRange) {
- EXPECT_EQ(2, java_util::JavaAdd(1, 1));
-}
-
-TEST(JavaUtilTest, TestJavaAdd_Underflow) {
- EXPECT_EQ(kMaxInt32, java_util::JavaAdd(kMinInt32, -1));
- EXPECT_EQ(kMaxInt32 - 1, java_util::JavaAdd(kMinInt32, -2));
- EXPECT_EQ(1, java_util::JavaAdd(kMinInt32, -kMaxInt32));
-}
-
-TEST(JavaUtilTest, TestJavaAdd_Overflow) {
- EXPECT_EQ(kMinInt32, java_util::JavaAdd(kMaxInt32, 1));
- EXPECT_EQ(kMinInt32 + 1, java_util::JavaAdd(kMaxInt32, 2));
- EXPECT_EQ(-2, java_util::JavaAdd(kMaxInt32, kMaxInt32));
-}
-
-TEST(JavaUtilTest, TestJavaMultiply_InRange) {
- EXPECT_EQ(4, java_util::JavaAdd(2, 2));
-}
-
-TEST(JavaUtilTest, TestJavaMultiply_Underflow) {
- EXPECT_EQ(0, java_util::JavaMultiply(kMinInt32, 2));
- EXPECT_EQ(-(kMinInt32 / 2), java_util::JavaMultiply(kMinInt32 / 2, 3));
- EXPECT_EQ(kMinInt32, java_util::JavaMultiply(kMinInt32, kMaxInt32));
-}
-
-TEST(JavaUtilTest, TestJavaMultiply_Overflow) {
- EXPECT_EQ(-2, java_util::JavaMultiply(kMaxInt32, 2));
- EXPECT_EQ(kMaxInt32 - 2, java_util::JavaMultiply(kMaxInt32, 3));
- EXPECT_EQ(1, java_util::JavaMultiply(kMaxInt32, kMaxInt32));
-}
-
-TEST(JavaUtilTest, TestJavaHashCode_EmptyBytes) {
- EXPECT_EQ(1, java_util::JavaHashCode(ByteBuffer()));
-}
-
-TEST(JavaUtilTest, TestJavaHashCode_LongByteArray) {
- const uint8_t kBytes[] = {
- 0x93, 0x75, 0xE1, 0x2E, 0x26, 0x28, 0x54, 0x8C, 0xD9, 0x5C, 0x48, 0x7A,
- 0x07, 0x53, 0x4E, 0xED, 0x28, 0x52, 0x5D, 0x41, 0xE3, 0x18, 0x84, 0x84,
- 0x5F, 0xF6, 0x89, 0x98, 0x25, 0x1E, 0xD9, 0x6C, 0x85, 0xF3, 0x5A, 0x83,
- 0x39, 0x37, 0x4E, 0x77, 0x95, 0xB5, 0x58, 0x7C, 0xD2, 0x55, 0xA0, 0x86,
- 0x13, 0x3F, 0xBF, 0x85, 0xD3, 0xE0, 0x28, 0x90, 0x17, 0x3D, 0x2E, 0xD4,
- 0x4D, 0x95, 0x9C, 0xAE, 0xAD, 0x8A, 0x05, 0x91, 0x5D, 0xC6, 0x4B, 0x09,
- 0xB2, 0xD9, 0x34, 0x64, 0x07, 0x7B, 0x07, 0x8C, 0xA6, 0xC7, 0x1C, 0x10,
- 0x34, 0xD4, 0x30, 0x80, 0x03, 0x4F, 0x2C, 0x70};
- const int32_t kExpectedHashCode = 1983685004;
- EXPECT_EQ(kExpectedHashCode,
- java_util::JavaHashCode(ByteBuffer(kBytes, sizeof(kBytes))));
-}
-
-} // namespace securegcm
diff --git a/src/main/java/com/google/security/annotations/CryptoAnnotation.java b/src/main/java/com/google/security/annotations/CryptoAnnotation.java
deleted file mode 100644
index 7f4230d..0000000
--- a/src/main/java/com/google/security/annotations/CryptoAnnotation.java
+++ /dev/null
@@ -1,68 +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
- *
- * https://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.
- */
-// Copyright 2007 Google Inc. All Rights Reserved
-
-package com.google.security.annotations;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Crypto Key Annotation: Label any cryptographic keys in code with this
- * annotation. This will help identify cryptographic keys that are exposed in
- * source code. Keys in source code should be annotated with an owner, purpose,
- * removal priority, and leak severity.
- *
- * Example of usage:
- * @CryptoAnnotation(
- * purpose = CryptoAnnotation.Purpose.AUTHENTICATION,
- * owner = "sweis",
- * bugId = 7041243,
- * leakSeverity = CryptoAnnotation.LeakSeverity.S2,
- * removalPriority = CryptoAnnotation.RemovalPriority.P1,
- * description = "This key is used to sign blah blah blah."
- * removalDate = "9/2007
- * )
- * byte[] keyBytes = {0xDE, 0xAD, 0xBE, 0xEF};
- *
- * @author sweis@google.com (Steve Weis)
- */
-@Retention(RetentionPolicy.SOURCE)
-@Target({ElementType.FIELD, ElementType.LOCAL_VARIABLE})
-public @interface CryptoAnnotation {
- /*
- * Keys with "encryption" and "authentication" purposes should be removed
- * from source code.
- *
- * Keys with "obfuscation" and "integrity check" purposes do not necessarily
- * need to be cryptographically strong. They may or may not be removed from
- * code at the discretion of the code owner.
- */
- public enum Purpose {ENCRYPTION, AUTHENTICATION, OBFUSCATION,
- INTEGRITY_CHECK, PASSWORD, OTHER}
- public enum LeakSeverity {S0, S1, S2, S3, S4, NoRisk}
- public enum RemovalPriority {P0, P1, P2, P3, P4, WillNotFix}
-
- LeakSeverity leakSeverity();
- RemovalPriority removalPriority();
- int bugId() default 0;
- String owner(); // Will be contacted in the event a key is leaked
- Purpose purpose();
- String description() default "";
- String removalDate() default "";
-}
-
diff --git a/src/main/java/com/google/security/annotations/SuppressInsecureCipherModeCheckerNoReview.java b/src/main/java/com/google/security/annotations/SuppressInsecureCipherModeCheckerNoReview.java
deleted file mode 100644
index 93dc716..0000000
--- a/src/main/java/com/google/security/annotations/SuppressInsecureCipherModeCheckerNoReview.java
+++ /dev/null
@@ -1,57 +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
- *
- * https://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.security.annotations;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-import javax.crypto.Cipher;
-
-/**
- * This annotation is used to disable the InsecureCipherMode Error Prone checker for legacy code
- * that didn't undergo a security review by ISE.
- *
- * <p>A {@link Cipher} object is created using one of the overloads of the
- * {@link Cipher#getInstance()} method. This method takes a specification of the transformer either
- * as a triple "Algorithm/Mode/Padding" or just "Algorithm", using the provider's default settings.
- * The InsecureCipherMode checker implemented in Error Prone flags all call sites of
- * {@link Cipher#getInstance()}, where either the insecure ECB mode or the provider's default mode
- * is used. This method annotation is used to suppress the Error Prone checker for legacy code
- * without review by ISE. The annotation is BUILD-visibility restricted and every use must be vetted
- * by the ISE team.
- *
- * <p>Example of usage:
- * <pre>
- * {@code
- * @SuppressInsecureCipherModeCheckerNoReview
- * private String decrypt(String[] input) {
- * Cipher aesCipher = Cipher.getInstance("AES");
- * aesCipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(rawKeyMaterial, "AES"));
- * // ...
- * }
- * }
- * </pre>
- *
- * @author avenet@google.com (Arnaud J. Venet)
- *
- */
-@Documented
-@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.CONSTRUCTOR,
- ElementType.LOCAL_VARIABLE})
-@Retention(RetentionPolicy.SOURCE)
-public @interface SuppressInsecureCipherModeCheckerNoReview {}
diff --git a/src/main/java/com/google/security/annotations/SuppressInsecureCipherModeCheckerPendingReview.java b/src/main/java/com/google/security/annotations/SuppressInsecureCipherModeCheckerPendingReview.java
deleted file mode 100644
index a7957c1..0000000
--- a/src/main/java/com/google/security/annotations/SuppressInsecureCipherModeCheckerPendingReview.java
+++ /dev/null
@@ -1,59 +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
- *
- * https://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.security.annotations;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-import javax.crypto.Cipher;
-
-/**
- * This annotation is used to temporarily disable the InsecureCipherMode Error Prone checker while
- * the violation is being reviewed by ISE. A comment including a tracking bug for the ongoing
- * security review should accompany the annotation. If the specific use is deemed a valid exception
- * after review, the annotation should be changed to @SuppressInsecureCipherModeCheckerReviewed.
- *
- * <p>A {@link Cipher} object is created using one of the overloads of the
- * {@link Cipher#getInstance()} method. This method takes a specification of the transformer either
- * as a triple "Algorithm/Mode/Padding" or just "Algorithm", using the provider's default settings.
- * The InsecureCipherMode checker implemented in Error Prone flags all call sites of
- * {@link Cipher#getInstance()}, where either the insecure ECB mode or the provider's default mode
- * is used. This method annotation is used to suppress the Error Prone checker in use cases where an
- * exception has been granted by ISE after proper review. The annotation is BUILD-visibility
- * restricted and every use must be vetted by the ISE team.
- *
- * <p>Example of usage:
- * <pre>
- * {@code
- * @SuppressInsecureCipherModeCheckerPendingReview // Tracking bug for the review: b/...
- * private String decrypt(String[] input) {
- * Cipher aesCipher = Cipher.getInstance("AES");
- * aesCipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(rawKeyMaterial, "AES"));
- * // ...
- * }
- * }
- * </pre>
- *
- * @author avenet@google.com (Arnaud J. Venet)
- *
- */
-@Documented
-@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.CONSTRUCTOR,
- ElementType.LOCAL_VARIABLE})
-@Retention(RetentionPolicy.SOURCE)
-public @interface SuppressInsecureCipherModeCheckerPendingReview {}
diff --git a/src/main/java/com/google/security/annotations/SuppressInsecureCipherModeCheckerReviewed.java b/src/main/java/com/google/security/annotations/SuppressInsecureCipherModeCheckerReviewed.java
deleted file mode 100644
index d1780c5..0000000
--- a/src/main/java/com/google/security/annotations/SuppressInsecureCipherModeCheckerReviewed.java
+++ /dev/null
@@ -1,58 +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
- *
- * https://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.security.annotations;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-import javax.crypto.Cipher;
-
-/**
- * This annotation is used to disable the InsecureCipherMode Error Prone checker after a proper
- * review by ISE. A comment including a tracking bug for the security review should accompany the
- * annotation.
- *
- * <p>A {@link Cipher} object is created using one of the overloads of the
- * {@link Cipher#getInstance()} method. This method takes a specification of the transformer either
- * as a triple "Algorithm/Mode/Padding" or just "Algorithm", using the provider's default settings.
- * The InsecureCipherMode checker implemented in Error Prone flags all call sites of
- * {@link Cipher#getInstance()}, where either the insecure ECB mode or the provider's default mode
- * is used. This method annotation is used to suppress the Error Prone checker in use cases where an
- * exception has been granted by ISE after proper review. The annotation is BUILD-visibility
- * restricted and every use must be vetted by the ISE team.
- *
- * <p>Example of usage:
- * <pre>
- * {@code
- * @SuppressInsecureCipherModeCheckerReviewed // Tracking bug for the review: b/...
- * private String decrypt(String[] input) {
- * Cipher aesCipher = Cipher.getInstance("AES");
- * aesCipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(rawKeyMaterial, "AES"));
- * // ...
- * }
- * }
- * </pre>
- *
- * @author avenet@google.com (Arnaud J. Venet)
- *
- */
-@Documented
-@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.CONSTRUCTOR,
- ElementType.LOCAL_VARIABLE})
-@Retention(RetentionPolicy.SOURCE)
-public @interface SuppressInsecureCipherModeCheckerReviewed {}
diff --git a/src/main/java/com/google/security/cryptauth/lib/securegcm/D2DConnectionContext.java b/src/main/java/com/google/security/cryptauth/lib/securegcm/D2DConnectionContext.java
deleted file mode 100644
index fb4af63..0000000
--- a/src/main/java/com/google/security/cryptauth/lib/securegcm/D2DConnectionContext.java
+++ /dev/null
@@ -1,274 +0,0 @@
-// Copyright 2020 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
-//
-// https://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.security.cryptauth.lib.securegcm;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.protobuf.ByteString;
-import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.security.cryptauth.lib.securegcm.DeviceToDeviceMessagesProto.DeviceToDeviceMessage;
-import com.google.security.cryptauth.lib.securegcm.TransportCryptoOps.Payload;
-import com.google.security.cryptauth.lib.securegcm.TransportCryptoOps.PayloadType;
-import java.io.UnsupportedEncodingException;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.security.SignatureException;
-import java.util.Arrays;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.SecretKeySpec;
-
-/**
- * The full context of a secure connection. This object has methods to encode and decode messages
- * that are to be sent to another device.
- *
- * Subclasses keep track of the keys shared with the other device, and of the sequence in which the
- * messages are expected.
- */
-public abstract class D2DConnectionContext {
- private static final String UTF8 = "UTF-8";
- private final int protocolVersion;
-
- protected D2DConnectionContext(int protocolVersion) {
- this.protocolVersion = protocolVersion;
- }
-
- /**
- * @return the version of the D2D protocol.
- */
- public int getProtocolVersion() {
- return protocolVersion;
- }
-
- /**
- * Once initiator and responder have exchanged public keys, use this method to encrypt and
- * sign a payload. Both initiator and responder devices can use this message.
- *
- * @param payload the payload that should be encrypted.
- */
- public byte[] encodeMessageToPeer(byte[] payload) {
- incrementSequenceNumberForEncoding();
- DeviceToDeviceMessage message = createDeviceToDeviceMessage(
- payload, getSequenceNumberForEncoding());
- try {
- return D2DCryptoOps.signcryptPayload(
- new Payload(PayloadType.DEVICE_TO_DEVICE_MESSAGE,
- message.toByteArray()),
- getEncodeKey());
- } catch (InvalidKeyException e) {
- // should never happen, since we agreed on the key earlier
- throw new RuntimeException(e);
- } catch (NoSuchAlgorithmException e) {
- // should never happen
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Encrypting/signing a string for transmission to another device.
- *
- * @see #encodeMessageToPeer(byte[])
- *
- * @param payload the payload that should be encrypted.
- */
- public byte[] encodeMessageToPeer(String payload) {
- try {
- return encodeMessageToPeer(payload.getBytes(UTF8));
- } catch (UnsupportedEncodingException e) {
- // Should never happen - we should always be able to UTF-8-encode a string
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Once InitiatorHello and ResponderHello(AndPayload) are exchanged, use this method
- * to decrypt and verify a message received from the other device. Both initiator and
- * responder device can use this message.
- *
- * @param message the message that should be encrypted.
- * @throws SignatureException if the message from the remote peer did not pass verification
- */
- public byte[] decodeMessageFromPeer(byte[] message) throws SignatureException {
- try {
- Payload payload = D2DCryptoOps.verifydecryptPayload(message, getDecodeKey());
- if (!PayloadType.DEVICE_TO_DEVICE_MESSAGE.equals(payload.getPayloadType())) {
- throw new SignatureException("wrong message type in device-to-device message");
- }
-
- DeviceToDeviceMessage messageProto = DeviceToDeviceMessage.parseFrom(payload.getMessage());
- incrementSequenceNumberForDecoding();
- if (messageProto.getSequenceNumber() != getSequenceNumberForDecoding()) {
- throw new SignatureException("Incorrect sequence number");
- }
-
- return messageProto.getMessage().toByteArray();
- } catch (InvalidKeyException e) {
- throw new SignatureException(e);
- } catch (NoSuchAlgorithmException e) {
- // this shouldn't happen - the algorithms are hard-coded.
- throw new RuntimeException(e);
- } catch (InvalidProtocolBufferException e) {
- throw new SignatureException(e);
- }
- }
-
- /**
- * Once InitiatorHello and ResponderHello(AndPayload) are exchanged, use this method
- * to decrypt and verify a message received from the other device. Both initiator and
- * responder device can use this message.
- *
- * @param message the message that should be encrypted.
- */
- public String decodeMessageFromPeerAsString(byte[] message) throws SignatureException {
- try {
- return new String(decodeMessageFromPeer(message), UTF8);
- } catch (UnsupportedEncodingException e) {
- // Should never happen - we should always be able to UTF-8-encode a string
- throw new RuntimeException(e);
- }
- }
-
- // package-private
- static DeviceToDeviceMessage createDeviceToDeviceMessage(byte[] message, int sequenceNumber) {
- DeviceToDeviceMessage.Builder deviceToDeviceMessage = DeviceToDeviceMessage.newBuilder();
- deviceToDeviceMessage.setSequenceNumber(sequenceNumber);
- deviceToDeviceMessage.setMessage(ByteString.copyFrom(message));
- return deviceToDeviceMessage.build();
- }
-
- /**
- * Returns a cryptographic digest (SHA256) of the session keys prepended by the SHA256 hash
- * of the ASCII string "D2D"
- * @throws NoSuchAlgorithmException if SHA 256 doesn't exist on this platform
- */
- public abstract byte[] getSessionUnique() throws NoSuchAlgorithmException;
-
- /**
- * Increments the sequence number used for encoding messages.
- */
- protected abstract void incrementSequenceNumberForEncoding();
-
- /**
- * Increments the sequence number used for decoding messages.
- */
- protected abstract void incrementSequenceNumberForDecoding();
-
- /**
- * @return the last sequence number used to encode a message.
- */
- @VisibleForTesting
- abstract int getSequenceNumberForEncoding();
-
- /**
- * @return the last sequence number used to decode a message.
- */
- @VisibleForTesting
- abstract int getSequenceNumberForDecoding();
-
- /**
- * @return the {@link SecretKey} used for encoding messages.
- */
- @VisibleForTesting
- abstract SecretKey getEncodeKey();
-
- /**
- * @return the {@link SecretKey} used for decoding messages.
- */
- @VisibleForTesting
- abstract SecretKey getDecodeKey();
-
- /**
- * Creates a saved session that can later be used for resumption. Note, this must be stored in a
- * secure location.
- *
- * @return the saved session, suitable for resumption.
- */
- public abstract byte[] saveSession();
-
- /**
- * Parse a saved session info and attempt to construct a resumed context.
- * The first byte in a saved session info must always be the protocol version.
- * Note that an {@link IllegalArgumentException} will be thrown if the savedSessionInfo is not
- * properly formatted.
- *
- * @return a resumed context from a saved session.
- */
- public static D2DConnectionContext fromSavedSession(byte[] savedSessionInfo) {
- if (savedSessionInfo == null || savedSessionInfo.length == 0) {
- throw new IllegalArgumentException("savedSessionInfo null or too short");
- }
-
- int protocolVersion = savedSessionInfo[0] & 0xff;
-
- switch (protocolVersion) {
- case 0:
- // Version 0 has a 1 byte protocol version, a 4 byte sequence number,
- // and 32 bytes of AES key (1 + 4 + 32 = 37)
- if (savedSessionInfo.length != 37) {
- throw new IllegalArgumentException("Incorrect data length (" + savedSessionInfo.length
- + ") for v0 protocol");
- }
- int sequenceNumber = bytesToSignedInt(Arrays.copyOfRange(savedSessionInfo, 1, 5));
- SecretKey sharedKey = new SecretKeySpec(Arrays.copyOfRange(savedSessionInfo, 5, 37), "AES");
- return new D2DConnectionContextV0(sharedKey, sequenceNumber);
-
- case 1:
- // Version 1 has a 1 byte protocol version, two 4 byte sequence numbers,
- // and two 32 byte AES keys (1 + 4 + 4 + 32 + 32 = 73)
- if (savedSessionInfo.length != 73) {
- throw new IllegalArgumentException("Incorrect data length for v1 protocol");
- }
- int encodeSequenceNumber = bytesToSignedInt(Arrays.copyOfRange(savedSessionInfo, 1, 5));
- int decodeSequenceNumber = bytesToSignedInt(Arrays.copyOfRange(savedSessionInfo, 5, 9));
- SecretKey encodeKey =
- new SecretKeySpec(Arrays.copyOfRange(savedSessionInfo, 9, 41), "AES");
- SecretKey decodeKey =
- new SecretKeySpec(Arrays.copyOfRange(savedSessionInfo, 41, 73), "AES");
- return new D2DConnectionContextV1(encodeKey, decodeKey, encodeSequenceNumber,
- decodeSequenceNumber);
-
- default:
- throw new IllegalArgumentException("Cannot rebuild context, unkown protocol version: "
- + protocolVersion);
- }
- }
-
- /**
- * Convert 4 bytes in big-endian representation into a signed int.
- */
- static int bytesToSignedInt(byte[] bytes) {
- if (bytes.length != 4) {
- throw new IllegalArgumentException("Expected 4 bytes to encode int, but got: "
- + bytes.length + " bytes");
- }
-
- return ((bytes[0] << 24) & 0xff000000)
- | ((bytes[1] << 16) & 0x00ff0000)
- | ((bytes[2] << 8) & 0x0000ff00)
- | (bytes[3] & 0x000000ff);
- }
-
- /**
- * Convert a signed int into a 4 byte big-endian representation
- */
- static byte[] signedIntToBytes(int val) {
- byte[] bytes = new byte[4];
-
- bytes[0] = (byte) ((val >> 24) & 0xff);
- bytes[1] = (byte) ((val >> 16) & 0xff);
- bytes[2] = (byte) ((val >> 8) & 0xff);
- bytes[3] = (byte) (val & 0xff);
-
- return bytes;
- }
-}
diff --git a/src/main/java/com/google/security/cryptauth/lib/securegcm/D2DConnectionContextV0.java b/src/main/java/com/google/security/cryptauth/lib/securegcm/D2DConnectionContextV0.java
deleted file mode 100644
index d0efa44..0000000
--- a/src/main/java/com/google/security/cryptauth/lib/securegcm/D2DConnectionContextV0.java
+++ /dev/null
@@ -1,118 +0,0 @@
-// Copyright 2020 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
-//
-// https://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.security.cryptauth.lib.securegcm;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import javax.crypto.SecretKey;
-
-/**
- * Implementation of {@link D2DConnectionContext} for version 0 of the D2D protocol. In this
- * version, communication is half-duplex, as there is a shared key and a shared sequence number
- * between the two sides.
- */
-public class D2DConnectionContextV0 extends D2DConnectionContext {
- public static final int PROTOCOL_VERSION = 0;
-
- private final SecretKey sharedKey;
- private int sequenceNumber;
-
- /**
- * Package private constructor. Should never be called directly except by the
- * {@link D2DHandshakeContext}
- *
- * @param sharedKey
- * @param initialSequenceNumber
- */
- D2DConnectionContextV0(SecretKey sharedKey, int initialSequenceNumber) {
- super(PROTOCOL_VERSION);
- this.sharedKey = sharedKey;
- this.sequenceNumber = initialSequenceNumber;
- }
-
- @Override
- public byte[] getSessionUnique() throws NoSuchAlgorithmException {
- if (sharedKey == null) {
- throw new IllegalStateException(
- "Connection has not been correctly initialized; shared key is null");
- }
-
- MessageDigest md = MessageDigest.getInstance("SHA-256");
- md.update(D2DCryptoOps.SALT);
- return md.digest(sharedKey.getEncoded());
- }
-
- @Override
- protected void incrementSequenceNumberForEncoding() {
- sequenceNumber++;
- }
-
- @Override
- protected void incrementSequenceNumberForDecoding() {
- sequenceNumber++;
- }
-
- @Override
- int getSequenceNumberForEncoding() {
- return sequenceNumber;
- }
-
- @Override
- int getSequenceNumberForDecoding() {
- return sequenceNumber;
- }
-
- @Override
- SecretKey getEncodeKey() {
- return sharedKey;
- }
-
- @Override
- SecretKey getDecodeKey() {
- return sharedKey;
- }
-
- /**
- * Structure of saved session is:
- * +-----------------------------------------------------+
- * | 1 Byte | 4 Bytes (big endian) | 32 Bytes |
- * +-----------------------------------------------------+
- * | Protocol Version | sequence number | key |
- * +-----------------------------------------------------+
- */
- @Override
- public byte[] saveSession() {
- ByteArrayOutputStream bytes = new ByteArrayOutputStream();
-
- try {
- // Protocol version
- bytes.write(0);
-
- // Sequence number
- bytes.write(signedIntToBytes(sequenceNumber));
-
- // Key
- bytes.write(sharedKey.getEncoded());
- } catch (IOException e) {
- // should not happen
- e.printStackTrace();
- return null;
- }
-
- return bytes.toByteArray();
- }
-}
diff --git a/src/main/java/com/google/security/cryptauth/lib/securegcm/D2DConnectionContextV1.java b/src/main/java/com/google/security/cryptauth/lib/securegcm/D2DConnectionContextV1.java
deleted file mode 100644
index 1566849..0000000
--- a/src/main/java/com/google/security/cryptauth/lib/securegcm/D2DConnectionContextV1.java
+++ /dev/null
@@ -1,146 +0,0 @@
-// Copyright 2020 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
-//
-// https://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.security.cryptauth.lib.securegcm;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.Arrays;
-import javax.crypto.SecretKey;
-
-/**
- * Implementation of {@link D2DConnectionContext} for version 1 of the D2D protocol. In this
- * version, communication is fully duplex, as separate keys and sequence nubmers are used for
- * encoding and decoding.
- */
-public class D2DConnectionContextV1 extends D2DConnectionContext {
- public static final int PROTOCOL_VERSION = 1;
-
- private final SecretKey encodeKey;
- private final SecretKey decodeKey;
- private int encodeSequenceNumber;
- private int decodeSequenceNumber;
-
- /**
- * Package private constructor. Should never be called directly except by the
- * {@link D2DHandshakeContext}
- *
- * @param encodeKey
- * @param decodeKey
- * @param initialEncodeSequenceNumber
- * @param initialDecodeSequenceNumber
- */
- D2DConnectionContextV1(
- SecretKey encodeKey,
- SecretKey decodeKey,
- int initialEncodeSequenceNumber,
- int initialDecodeSequenceNumber) {
- super(PROTOCOL_VERSION);
- this.encodeKey = encodeKey;
- this.decodeKey = decodeKey;
- this.encodeSequenceNumber = initialEncodeSequenceNumber;
- this.decodeSequenceNumber = initialDecodeSequenceNumber;
- }
-
- @Override
- public byte[] getSessionUnique() throws NoSuchAlgorithmException {
- if (encodeKey == null || decodeKey == null) {
- throw new IllegalStateException(
- "Connection has not been correctly initialized; encode key or decode key is null");
- }
-
- // Ensure that the initator and responder keys are hashed in a deterministic order, so they have
- // the same session unique code.
- byte[] encodeKeyBytes = encodeKey.getEncoded();
- byte[] decodeKeyBytes = decodeKey.getEncoded();
- int encodeKeyHash = Arrays.hashCode(encodeKeyBytes);
- int decodeKeyHash = Arrays.hashCode(decodeKeyBytes);
- byte[] firstKeyBytes = encodeKeyHash < decodeKeyHash ? encodeKeyBytes : decodeKeyBytes;
- byte[] secondKeyBytes = firstKeyBytes == encodeKeyBytes ? decodeKeyBytes : encodeKeyBytes;
-
- MessageDigest md = MessageDigest.getInstance("SHA-256");
- md.update(D2DCryptoOps.SALT);
- md.update(firstKeyBytes);
- md.update(secondKeyBytes);
- return md.digest();
- }
-
- @Override
- protected void incrementSequenceNumberForEncoding() {
- encodeSequenceNumber++;
- }
-
- @Override
- protected void incrementSequenceNumberForDecoding() {
- decodeSequenceNumber++;
- }
-
- @Override
- int getSequenceNumberForEncoding() {
- return encodeSequenceNumber;
- }
-
- @Override
- int getSequenceNumberForDecoding() {
- return decodeSequenceNumber;
- }
-
- @Override
- SecretKey getEncodeKey() {
- return encodeKey;
- }
-
- @Override
- SecretKey getDecodeKey() {
- return decodeKey;
- }
-
- /**
- * Structure of saved session is:
- * +------------------------------------------------------------------------------------------+
- * | 1 Byte | 4 Bytes (big endian) | 4 Bytes (big endian) | 32 Bytes | 32 Bytes |
- * +------------------------------------------------------------------------------------------+
- * | Protocol Version | encode seq number | decode seq number | encode key | decode key |
- * +------------------------------------------------------------------------------------------+
- */
- @Override
- public byte[] saveSession() {
- ByteArrayOutputStream bytes = new ByteArrayOutputStream();
-
- try {
- // Protocol version
- bytes.write(1);
-
- // Encode sequence number
- bytes.write(signedIntToBytes(encodeSequenceNumber));
-
- // Decode sequence number
- bytes.write(signedIntToBytes(decodeSequenceNumber));
-
- // Encode Key
- bytes.write(encodeKey.getEncoded());
-
- // Decode Key
- bytes.write(decodeKey.getEncoded());
- } catch (IOException e) {
- // should not happen
- e.printStackTrace();
- return null;
- }
-
- return bytes.toByteArray();
- }
-}
diff --git a/src/main/java/com/google/security/cryptauth/lib/securegcm/D2DCryptoOps.java b/src/main/java/com/google/security/cryptauth/lib/securegcm/D2DCryptoOps.java
deleted file mode 100644
index a7203d1..0000000
--- a/src/main/java/com/google/security/cryptauth/lib/securegcm/D2DCryptoOps.java
+++ /dev/null
@@ -1,239 +0,0 @@
-// Copyright 2020 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
-//
-// https://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.security.cryptauth.lib.securegcm;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.security.cryptauth.lib.securegcm.DeviceToDeviceMessagesProto.DeviceToDeviceMessage;
-import com.google.security.cryptauth.lib.securegcm.DeviceToDeviceMessagesProto.ResponderHello;
-import com.google.security.cryptauth.lib.securegcm.SecureGcmProto.GcmMetadata;
-import com.google.security.cryptauth.lib.securegcm.TransportCryptoOps.Payload;
-import com.google.security.cryptauth.lib.securegcm.TransportCryptoOps.PayloadType;
-import com.google.security.cryptauth.lib.securemessage.CryptoOps;
-import com.google.security.cryptauth.lib.securemessage.CryptoOps.EncType;
-import com.google.security.cryptauth.lib.securemessage.CryptoOps.SigType;
-import com.google.security.cryptauth.lib.securemessage.PublicKeyProtoUtil;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageBuilder;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageParser;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.GenericPublicKey;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.Header;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.HeaderAndBody;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.SecureMessage;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.SignatureException;
-import java.security.spec.InvalidKeySpecException;
-import javax.annotation.Nullable;
-import javax.crypto.SecretKey;
-
-/**
- * A collection of static utility methods used by {@link D2DHandshakeContext} for the Device to
- * Device communication (D2D) library.
- */
-class D2DCryptoOps {
- // SHA256 of "D2D"
- // package-private
- static final byte[] SALT = new byte[] {
- (byte) 0x82, (byte) 0xAA, (byte) 0x55, (byte) 0xA0, (byte) 0xD3, (byte) 0x97, (byte) 0xF8,
- (byte) 0x83, (byte) 0x46, (byte) 0xCA, (byte) 0x1C, (byte) 0xEE, (byte) 0x8D, (byte) 0x39,
- (byte) 0x09, (byte) 0xB9, (byte) 0x5F, (byte) 0x13, (byte) 0xFA, (byte) 0x7D, (byte) 0xEB,
- (byte) 0x1D, (byte) 0x4A, (byte) 0xB3, (byte) 0x83, (byte) 0x76, (byte) 0xB8, (byte) 0x25,
- (byte) 0x6D, (byte) 0xA8, (byte) 0x55, (byte) 0x10
- };
-
- // Data passed to hkdf to create the key used by the initiator to encode messages.
- static final String INITIATOR_PURPOSE = "initiator";
- // Data passed to hkdf to create the key used by the responder to encode messages.
- static final String RESPONDER_PURPOSE = "responder";
-
- // Don't instantiate
- private D2DCryptoOps() { }
-
- /**
- * Used by the responder device to create a signcrypted message that contains
- * a payload and a {@link ResponderHello}.
- *
- * @param sharedKey used to signcrypt the {@link Payload}
- * @param publicDhKey the key the recipient will need to derive the shared DH secret.
- * This key will be added to the {@link ResponderHello} in the header.
- * @param protocolVersion the protocol version to include in the proto
- */
- static byte[] signcryptMessageAndResponderHello(
- Payload payload, SecretKey sharedKey, PublicKey publicDhKey, int protocolVersion)
- throws InvalidKeyException, NoSuchAlgorithmException {
- ResponderHello.Builder responderHello = ResponderHello.newBuilder();
- responderHello.setPublicDhKey(PublicKeyProtoUtil.encodePublicKey(publicDhKey));
- responderHello.setProtocolVersion(protocolVersion);
- return signcryptPayload(payload, sharedKey, responderHello.build().toByteArray());
- }
-
- /**
- * Used by a device to send a secure {@link Payload} to another device.
- */
- static byte[] signcryptPayload(
- Payload payload, SecretKey masterKey)
- throws InvalidKeyException, NoSuchAlgorithmException {
- return signcryptPayload(payload, masterKey, null);
- }
-
- /**
- * Used by a device to send a secure {@link Payload} to another device.
- *
- * @param responderHello is an optional public value to attach in the header of
- * the {@link SecureMessage} (in the DecryptionKeyId).
- */
- @VisibleForTesting
- static byte[] signcryptPayload(
- Payload payload, SecretKey masterKey, @Nullable byte[] responderHello)
- throws InvalidKeyException, NoSuchAlgorithmException {
- if ((payload == null) || (masterKey == null)) {
- throw new NullPointerException();
- }
-
- SecureMessageBuilder secureMessageBuilder = new SecureMessageBuilder()
- .setPublicMetadata(GcmMetadata.newBuilder()
- .setType(payload.getPayloadType().getType())
- .setVersion(SecureGcmConstants.SECURE_GCM_VERSION)
- .build()
- .toByteArray());
-
- if (responderHello != null) {
- secureMessageBuilder.setDecryptionKeyId(responderHello);
- }
-
- return secureMessageBuilder.buildSignCryptedMessage(
- masterKey,
- SigType.HMAC_SHA256,
- masterKey,
- EncType.AES_256_CBC,
- payload.getMessage())
- .toByteArray();
- }
-
- /**
- * Extracts a ResponderHello proto from the header of a signcrypted message so that we
- * can derive the shared secret that was used to sign/encrypt the message.
- *
- * @return the {@link ResponderHello} embedded in the signcrypted message.
- */
- static ResponderHello parseAndValidateResponderHello(
- byte[] signcryptedMessageFromResponder) throws InvalidProtocolBufferException {
- if (signcryptedMessageFromResponder == null) {
- throw new NullPointerException();
- }
- SecureMessage secmsg = SecureMessage.parseFrom(signcryptedMessageFromResponder);
- Header messageHeader = SecureMessageParser.getUnverifiedHeader(secmsg);
- if (!messageHeader.hasDecryptionKeyId()) {
- // Maybe this should be a different exception type, because in general, it's legal for the
- // SecureMessage proto to not have the decryption key id, but it's illegal in this protocol.
- throw new InvalidProtocolBufferException("Missing decryption key id");
- }
- byte[] encodedResponderHello = messageHeader.getDecryptionKeyId().toByteArray();
- ResponderHello responderHello = ResponderHello.parseFrom(encodedResponderHello);
- if (!responderHello.hasPublicDhKey()) {
- throw new InvalidProtocolBufferException("Missing public key in responder hello");
- }
- return responderHello;
- }
-
- /**
- * Used by a device to recover a secure {@link Payload} sent by another device.
- */
- static Payload verifydecryptPayload(
- byte[] signcryptedMessage, SecretKey masterKey)
- throws SignatureException, InvalidKeyException, NoSuchAlgorithmException {
- if ((signcryptedMessage == null) || (masterKey == null)) {
- throw new NullPointerException();
- }
- try {
- SecureMessage secmsg = SecureMessage.parseFrom(signcryptedMessage);
- HeaderAndBody parsed = SecureMessageParser.parseSignCryptedMessage(
- secmsg,
- masterKey,
- SigType.HMAC_SHA256,
- masterKey,
- EncType.AES_256_CBC);
- if (!parsed.getHeader().hasPublicMetadata()) {
- throw new SignatureException("missing metadata");
- }
- GcmMetadata metadata = GcmMetadata.parseFrom(parsed.getHeader().getPublicMetadata());
- if (metadata.getVersion() > SecureGcmConstants.SECURE_GCM_VERSION) {
- throw new SignatureException("Unsupported protocol version");
- }
- return new Payload(PayloadType.valueOf(metadata.getType()), parsed.getBody().toByteArray());
- } catch (InvalidProtocolBufferException e) {
- throw new SignatureException(e);
- } catch (IllegalArgumentException e) {
- throw new SignatureException(e);
- }
- }
-
- /**
- * Used by the initiator device to derive the shared key from the {@link PrivateKey} in the
- * {@link D2DHandshakeContext} and the responder's {@link GenericPublicKey} (contained in the
- * {@link ResponderHello} proto).
- */
- static SecretKey deriveSharedKeyFromGenericPublicKey(
- PrivateKey ourPrivateKey, GenericPublicKey theirGenericPublicKey) throws SignatureException {
- try {
- PublicKey theirPublicKey = PublicKeyProtoUtil.parsePublicKey(theirGenericPublicKey);
- return EnrollmentCryptoOps.doKeyAgreement(ourPrivateKey, theirPublicKey);
- } catch (InvalidKeySpecException e) {
- throw new SignatureException(e);
- } catch (InvalidKeyException e) {
- throw new SignatureException(e);
- }
- }
-
- /**
- * Used to derive a distinct key for each initiator and responder.
- *
- * @param masterKey the source key used to derive the new key.
- * @param purpose a string to make the new key different for each purpose.
- * @return the derived {@link SecretKey}.
- */
- static SecretKey deriveNewKeyForPurpose(SecretKey masterKey, String purpose)
- throws NoSuchAlgorithmException, InvalidKeyException {
- byte[] info = purpose.getBytes();
- return KeyEncoding.parseMasterKey(CryptoOps.hkdf(masterKey, SALT, info));
- }
-
- /**
- * Used by the initiator device to decrypt the first payload portion that was sent in the
- * {@code responderHelloAndPayload}, and extract the {@link DeviceToDeviceMessage} contained
- * within it. In order to decrypt, the {@code sharedKey} must first be derived.
- *
- * @see #deriveSharedKeyFromGenericPublicKey(PrivateKey, GenericPublicKey)
- */
- static DeviceToDeviceMessage decryptResponderHelloMessage(
- SecretKey sharedKey, byte[] responderHelloAndPayload) throws SignatureException {
- try {
- Payload payload = verifydecryptPayload(responderHelloAndPayload, sharedKey);
- if (!PayloadType.DEVICE_TO_DEVICE_RESPONDER_HELLO_PAYLOAD.equals(
- payload.getPayloadType())) {
- throw new SignatureException("wrong message type in responder hello");
- }
- return DeviceToDeviceMessage.parseFrom(payload.getMessage());
- } catch (InvalidProtocolBufferException e) {
- throw new SignatureException(e);
- } catch (InvalidKeyException e) {
- throw new SignatureException(e);
- } catch (NoSuchAlgorithmException e) {
- throw new SignatureException(e);
- }
- }
-}
diff --git a/src/main/java/com/google/security/cryptauth/lib/securegcm/D2DDiffieHellmanKeyExchangeHandshake.java b/src/main/java/com/google/security/cryptauth/lib/securegcm/D2DDiffieHellmanKeyExchangeHandshake.java
deleted file mode 100644
index f929a3a..0000000
--- a/src/main/java/com/google/security/cryptauth/lib/securegcm/D2DDiffieHellmanKeyExchangeHandshake.java
+++ /dev/null
@@ -1,307 +0,0 @@
-// Copyright 2020 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
-//
-// https://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.security.cryptauth.lib.securegcm;
-
-import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.security.cryptauth.lib.securegcm.DeviceToDeviceMessagesProto.DeviceToDeviceMessage;
-import com.google.security.cryptauth.lib.securegcm.DeviceToDeviceMessagesProto.InitiatorHello;
-import com.google.security.cryptauth.lib.securegcm.DeviceToDeviceMessagesProto.ResponderHello;
-import com.google.security.cryptauth.lib.securegcm.TransportCryptoOps.Payload;
-import com.google.security.cryptauth.lib.securegcm.TransportCryptoOps.PayloadType;
-import com.google.security.cryptauth.lib.securemessage.PublicKeyProtoUtil;
-import java.security.InvalidKeyException;
-import java.security.KeyPair;
-import java.security.NoSuchAlgorithmException;
-import java.security.PublicKey;
-import java.security.SignatureException;
-import java.security.spec.InvalidKeySpecException;
-import javax.crypto.SecretKey;
-
-/**
- * Implements an unauthenticated EC Diffie Hellman Key Exchange Handshake
- * <p>
- * Initiator sends an InitiatorHello, which is a protobuf that contains a public key. Responder
- * sends a responder hello, which a signed and encrypted message containing a payload, and a public
- * key in the unencrypted header (payload is encrypted with the derived DH key).
- * <p>
- * Example Usage:
- * <pre>
- * // initiator:
- * D2DHandshakeContext initiatorHandshakeContext =
- * D2DDiffieHellmanKeyExchangeHandshake.forInitiator();
- * byte[] initiatorHello = initiatorHandshakeContext.getNextHandshakeMessage();
- * // (send initiatorHello to responder)
- *
- * // responder:
- * D2DHandshakeContext responderHandshakeContext =
- * D2DDiffieHellmanKeyExchangeHandshake.forResponder();
- * responderHandshakeContext.parseHandshakeMessage(initiatorHello);
- * byte[] responderHelloAndPayload = responderHandshakeContext.getNextHandshakeMessage(
- * toBytes(RESPONDER_HELLO_MESSAGE));
- * D2DConnectionContext responderCtx = responderHandshakeContext.toConnectionContext();
- * // (send responderHelloAndPayload to initiator)
- *
- * // initiator
- * byte[] messageFromPayload =
- * initiatorHandshakeContext.parseHandshakeMessage(responderHelloAndPayload);
- * if (messageFromPayload.length > 0) {
- * handle(messageFromPayload);
- * }
- *
- * D2DConnectionContext initiatorCtx = initiatorHandshakeContext.toConnectionContext();
- * </pre>
- */
-public class D2DDiffieHellmanKeyExchangeHandshake implements D2DHandshakeContext {
- private KeyPair ourKeyPair;
- private PublicKey theirPublicKey;
- private SecretKey initiatorEncodeKey;
- private SecretKey responderEncodeKey;
- private State handshakeState;
- private boolean isInitiator;
- private int protocolVersionToUse;
-
- private enum State {
- // Initiator state
- INITIATOR_START,
- INITIATOR_WAITING_FOR_RESPONDER_HELLO,
-
- // Responder state
- RESPONDER_START,
- RESPONDER_AFTER_INITIATOR_HELLO,
-
- // Common completion state
- HANDSHAKE_FINISHED,
- HANDSHAKE_ALREADY_USED
- }
-
- private D2DDiffieHellmanKeyExchangeHandshake(State state) {
- ourKeyPair = PublicKeyProtoUtil.generateEcP256KeyPair();
- theirPublicKey = null;
- initiatorEncodeKey = null;
- responderEncodeKey = null;
- handshakeState = state;
- isInitiator = state == State.INITIATOR_START;
- protocolVersionToUse = D2DConnectionContextV1.PROTOCOL_VERSION;
- }
-
- /**
- * Creates a new Diffie Hellman handshake context for the handshake initiator
- */
- public static D2DDiffieHellmanKeyExchangeHandshake forInitiator() {
- return new D2DDiffieHellmanKeyExchangeHandshake(State.INITIATOR_START);
- }
-
- /**
- * Creates a new Diffie Hellman handshake context for the handshake responder
- */
- public static D2DDiffieHellmanKeyExchangeHandshake forResponder() {
- return new D2DDiffieHellmanKeyExchangeHandshake(State.RESPONDER_START);
- }
-
- @Override
- public boolean isHandshakeComplete() {
- return handshakeState == State.HANDSHAKE_FINISHED
- || handshakeState == State.HANDSHAKE_ALREADY_USED;
- }
-
- @Override
- public byte[] getNextHandshakeMessage() throws HandshakeException {
- switch(handshakeState) {
- case INITIATOR_START:
- handshakeState = State.INITIATOR_WAITING_FOR_RESPONDER_HELLO;
- return InitiatorHello.newBuilder()
- .setPublicDhKey(PublicKeyProtoUtil.encodePublicKey(ourKeyPair.getPublic()))
- .setProtocolVersion(protocolVersionToUse)
- .build()
- .toByteArray();
-
- case RESPONDER_AFTER_INITIATOR_HELLO:
- byte[] responderHello = makeResponderHelloWithPayload(new byte[0]);
- handshakeState = State.HANDSHAKE_FINISHED;
- return responderHello;
-
- default:
- throw new HandshakeException("Cannot get next message in state: " + handshakeState);
- }
- }
-
- @Override
- public boolean canSendPayloadInHandshakeMessage() {
- return handshakeState == State.RESPONDER_AFTER_INITIATOR_HELLO;
- }
-
- @Override
- public byte[] getNextHandshakeMessage(byte[] payload) throws HandshakeException {
- if (handshakeState != State.RESPONDER_AFTER_INITIATOR_HELLO) {
- throw new HandshakeException(
- "Cannot get next message with payload in state: " + handshakeState);
- }
-
- byte[] responderHello = makeResponderHelloWithPayload(payload);
- handshakeState = State.HANDSHAKE_FINISHED;
-
- return responderHello;
- }
-
- private byte[] makeResponderHelloWithPayload(byte[] payload) throws HandshakeException {
- if (payload == null) {
- throw new HandshakeException("Not expecting null payload");
- }
-
- try {
- SecretKey masterKey =
- EnrollmentCryptoOps.doKeyAgreement(ourKeyPair.getPrivate(), theirPublicKey);
-
- // V0 uses the same key for encoding and decoding, but V1 uses separate keys.
- switch (protocolVersionToUse) {
- case D2DConnectionContextV0.PROTOCOL_VERSION:
- initiatorEncodeKey = masterKey;
- responderEncodeKey = masterKey;
- break;
- case D2DConnectionContextV1.PROTOCOL_VERSION:
- initiatorEncodeKey = D2DCryptoOps.deriveNewKeyForPurpose(masterKey,
- D2DCryptoOps.INITIATOR_PURPOSE);
- responderEncodeKey = D2DCryptoOps.deriveNewKeyForPurpose(masterKey,
- D2DCryptoOps.RESPONDER_PURPOSE);
- break;
- default:
- throw new IllegalStateException("Unexpected protocol version: " + protocolVersionToUse);
- }
-
- DeviceToDeviceMessage deviceToDeviceMessage =
- D2DConnectionContext.createDeviceToDeviceMessage(payload, 1 /* sequence number */);
-
- return D2DCryptoOps.signcryptMessageAndResponderHello(
- new Payload(PayloadType.DEVICE_TO_DEVICE_RESPONDER_HELLO_PAYLOAD,
- deviceToDeviceMessage.toByteArray()),
- responderEncodeKey,
- ourKeyPair.getPublic(),
- protocolVersionToUse);
- } catch (InvalidKeyException|NoSuchAlgorithmException e) {
- throw new HandshakeException(e);
- }
- }
-
- @Override
- public byte[] parseHandshakeMessage(byte[] handshakeMessage) throws HandshakeException {
- if (handshakeMessage == null || handshakeMessage.length == 0) {
- throw new HandshakeException("Handshake message too short");
- }
-
- switch(handshakeState) {
- case INITIATOR_WAITING_FOR_RESPONDER_HELLO:
- byte[] payload = parseResponderHello(handshakeMessage);
- handshakeState = State.HANDSHAKE_FINISHED;
- return payload;
-
- case RESPONDER_START:
- parseInitiatorHello(handshakeMessage);
- handshakeState = State.RESPONDER_AFTER_INITIATOR_HELLO;
- return new byte[0];
-
- default:
- throw new HandshakeException("Cannot parse message in state: " + handshakeState);
- }
- }
-
- private byte[] parseResponderHello(byte[] responderHello) throws HandshakeException {
- try {
- ResponderHello responderHelloProto =
- D2DCryptoOps.parseAndValidateResponderHello(responderHello);
-
- // Downgrade to protocol version 0 if needed for backwards compatibility.
- int protocolVersion = responderHelloProto.getProtocolVersion();
- if (protocolVersion == D2DConnectionContextV0.PROTOCOL_VERSION) {
- protocolVersionToUse = D2DConnectionContextV0.PROTOCOL_VERSION;
- }
-
- SecretKey masterKey = D2DCryptoOps.deriveSharedKeyFromGenericPublicKey(
- ourKeyPair.getPrivate(), responderHelloProto.getPublicDhKey());
-
- // V0 uses the same key for encoding and decoding, but V1 uses separate keys.
- if (protocolVersionToUse == D2DConnectionContextV0.PROTOCOL_VERSION) {
- initiatorEncodeKey = masterKey;
- responderEncodeKey = masterKey;
- } else {
- initiatorEncodeKey = D2DCryptoOps.deriveNewKeyForPurpose(masterKey,
- D2DCryptoOps.INITIATOR_PURPOSE);
- responderEncodeKey = D2DCryptoOps.deriveNewKeyForPurpose(masterKey,
- D2DCryptoOps.RESPONDER_PURPOSE);
- }
-
- DeviceToDeviceMessage message =
- D2DCryptoOps.decryptResponderHelloMessage(responderEncodeKey, responderHello);
-
- if (message.getSequenceNumber() != 1) {
- throw new HandshakeException("Incorrect sequence number in responder hello");
- }
-
- return message.getMessage().toByteArray();
- } catch (SignatureException | InvalidProtocolBufferException
- | NoSuchAlgorithmException | InvalidKeyException e) {
- throw new HandshakeException(e);
- }
- }
-
- private void parseInitiatorHello(byte[] initiatorHello) throws HandshakeException {
- try {
- InitiatorHello initiatorHelloProto = InitiatorHello.parseFrom(initiatorHello);
-
- if (!initiatorHelloProto.hasPublicDhKey()) {
- throw new HandshakeException("Missing public key in initiator hello");
- }
-
- theirPublicKey = PublicKeyProtoUtil.parsePublicKey(initiatorHelloProto.getPublicDhKey());
-
- // Downgrade to protocol version 0 if needed for backwards compatibility.
- int protocolVersion = initiatorHelloProto.getProtocolVersion();
- if (protocolVersion == D2DConnectionContextV0.PROTOCOL_VERSION) {
- protocolVersionToUse = D2DConnectionContextV0.PROTOCOL_VERSION;
- }
- } catch (InvalidKeySpecException | InvalidProtocolBufferException e) {
- throw new HandshakeException(e);
- }
- }
-
- @Override
- public D2DConnectionContext toConnectionContext() throws HandshakeException {
- if (handshakeState == State.HANDSHAKE_ALREADY_USED) {
- throw new HandshakeException("Cannot reuse handshake context; is has already been used");
- }
-
- if (!isHandshakeComplete()) {
- throw new HandshakeException("Handshake is not complete; cannot create connection context");
- }
-
- handshakeState = State.HANDSHAKE_ALREADY_USED;
-
- if (protocolVersionToUse == D2DConnectionContextV0.PROTOCOL_VERSION) {
- // Both sides start with an initial sequence number of 1 because the last message of the
- // handshake had an optional payload with sequence number 1. D2DConnectionContext remembers
- // the last sequence number used by each side.
- // Note: initiatorEncodeKey == responderEncodeKey
- return new D2DConnectionContextV0(initiatorEncodeKey, 1 /** initialSequenceNumber */);
- } else {
- SecretKey encodeKey = isInitiator ? initiatorEncodeKey : responderEncodeKey;
- SecretKey decodeKey = isInitiator ? responderEncodeKey : initiatorEncodeKey;
- // Only the responder sends a DeviceToDeviceMessage during the handshake, so it has an initial
- // sequence number of 1. The initiator will therefore have an initial sequence number of 0.
- int initialEncodeSequenceNumber = isInitiator ? 0 : 1;
- int initialDecodeSequenceNumber = isInitiator ? 1 : 0;
- return new D2DConnectionContextV1(
- encodeKey, decodeKey, initialEncodeSequenceNumber, initialDecodeSequenceNumber);
- }
- }
-}
diff --git a/src/main/java/com/google/security/cryptauth/lib/securegcm/D2DHandshakeContext.java b/src/main/java/com/google/security/cryptauth/lib/securegcm/D2DHandshakeContext.java
deleted file mode 100644
index 5fc1d7b..0000000
--- a/src/main/java/com/google/security/cryptauth/lib/securegcm/D2DHandshakeContext.java
+++ /dev/null
@@ -1,158 +0,0 @@
-// Copyright 2020 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
-//
-// https://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.security.cryptauth.lib.securegcm;
-
-/**
- * Describes a cryptographic handshake with arbitrary number of round trips. Some handshake
- * messages may also send a payload.
- *
- * <p>Generic usage for handshake Initiator:
- * {@code
- * // Handshake Initiator
- * D2DHandshakeContext handshake = <specific handshake>.forInitiator();
- * while (!handshake.isHandshakeComplete()) {
- * try {
- * // Get the next handshake message to send
- * byte[] initiatorMessage = handshake.getNextHandshakeMessage();
- *
- * // Send the message out and get the response
- * socket.send(initiatorMessage);
- * byte[] responderMessage = socket.read();
- *
- * // Handle the response and obtain the optional payload
- * byte[] payload = handshake.parseHandshakeMessage(responderMessage);
- *
- * // Handle the payload if one was sent
- * if (payload.length > 0) {
- * handlePayload(payload);
- * }
- * } catch (HandshakeException e) {
- * // Handshake has failed, bail
- * Log("Handshake failed!", e);
- * return;
- * }
- * }
- *
- * ConnectionContext connectionContext;
- * try {
- * // Upgrade handshake context to a full connection context
- * connectionContext = handshake.toConnectionContext();
- * } catch (HandshakeException e) {
- * Log("Cannot convert handshake to connection context", e);
- * }
- * }
- *
- * <p>Generic usage for handshake Responder:
- * {@code
- * // Handshake Responder
- * D2DHandshakeContext handshake = <specific handshake>.forResponder();
- *
- * while (!handshake.isHandshakeComplete()) {
- * try {
- * // Get the message from the initiator
- * byte[] initiatorMessage = socket.read();
- *
- * // Handle the message and get the payload if it exists
- * byte[] payload = handshake.parseHandshakeMessage(initiatorMessage);
- *
- * // Handle the payload if one was sent
- * if (payload.length > 0) {
- * handlePayload(payload);
- * }
- *
- * // Make sure that wasn't the last message
- * if (handshake.isHandshakeComplete()) {
- * break;
- * }
- *
- * // Get next message to send and send it
- * byte[] responderMessage = handshake.getNextHandshakeMessage();
- * socket.send(responderMessage);
- * } catch (HandshakeException e) {
- * // Handshake has failed, bail
- * Log("Handshake failed!", e);
- * return;
- * }
- * }
- *
- * ConnectionContext connectionContext;
- * try {
- * // Upgrade handshake context to a full connection context
- * connectionContext = handshake.toConnectionContext();
- * } catch (HandshakeException e) {
- * Log("Cannot convert handshake to connection context", e);
- * }
- * }
- */
-public interface D2DHandshakeContext {
-
- /**
- * Tells the caller whether the handshake has completed or not. If the handshake is complete, the
- * caller may call {@link #toConnectionContext()} to obtain a connection context.
- *
- * @return true if the handshake is complete, false otherwise
- */
- boolean isHandshakeComplete();
-
- /**
- * Constructs the next message that should be sent in the handshake.
- *
- * @return the next message
- * @throws HandshakeException if the handshake is over or if the next handshake message can't be
- * obtained (e.g., there is an internal error)
- */
- byte[] getNextHandshakeMessage() throws HandshakeException;
-
- /**
- * Tells the caller whether the next handshake message may carry a payload. If true, caller may
- * call {@link #getNextHandshakeMessage(byte[])} instead of the regular
- * {@link #getNextHandshakeMessage()}. If false, calling {@link #getNextHandshakeMessage(byte[])}
- * will result in a {@link HandshakeException}.
- *
- * @return true if the next handshake message can carry a payload, false otherwise
- */
- boolean canSendPayloadInHandshakeMessage();
-
- /**
- * Constructs the next message that should be sent in the handshake along with a payload. Caller
- * should verify that this method can be called by calling
- * {@link #canSendPayloadInHandshakeMessage()}.
- *
- * @param payload the payload to include in the handshake message
- * @return the next message
- * @throws HandshakeException if the handshake is over or if the next handshake message can't be
- * obtained (e.g., there is an internal error) or if the payload may not be included in this
- * message
- */
- byte[] getNextHandshakeMessage(byte[] payload) throws HandshakeException;
-
- /**
- * Parses a handshake message and returns the included payload (if any).
- *
- * @param handshakeMessage message received in the handshake
- * @return payload or empty byte[] if no payload was in the handshake message
- * @throws HandshakeException if an error occurs in parsing the handshake message
- */
- byte[] parseHandshakeMessage(byte[] handshakeMessage) throws HandshakeException;
-
- /**
- * Creates a full {@link D2DConnectionContext}. May only be called if
- * {@link #isHandshakeComplete()} returns true.
- *
- * @return a full {@link D2DConnectionContext}
- * @throws HandshakeException if a connection context cannot be created
- */
- D2DConnectionContext toConnectionContext() throws HandshakeException;
-}
diff --git a/src/main/java/com/google/security/cryptauth/lib/securegcm/Ed25519.java b/src/main/java/com/google/security/cryptauth/lib/securegcm/Ed25519.java
deleted file mode 100644
index 454b942..0000000
--- a/src/main/java/com/google/security/cryptauth/lib/securegcm/Ed25519.java
+++ /dev/null
@@ -1,270 +0,0 @@
-// Copyright 2020 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
-//
-// https://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.security.cryptauth.lib.securegcm;
-
-import static java.math.BigInteger.ONE;
-import static java.math.BigInteger.ZERO;
-
-import com.google.common.annotations.VisibleForTesting;
-import java.math.BigInteger;
-
-/**
- * Implements the Ed25519 twisted Edwards curve. See http://ed25519.cr.yp.to/ for more details.
- */
-public class Ed25519 {
-
- // Don't instantiate
- private Ed25519() { }
-
- // Curve parameters (http://ed25519.cr.yp.to/)
- private static final int HEX_RADIX = 16;
- private static final BigInteger Ed25519_P =
- new BigInteger("7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED", HEX_RADIX);
- private static final BigInteger Ed25519_D =
- new BigInteger("52036CEE2B6FFE738CC740797779E89800700A4D4141D8AB75EB4DCA135978A3", HEX_RADIX);
-
- // Helps to do fast addition k = 2*d
- private static final BigInteger Ed25519_K =
- new BigInteger("2406D9DC56DFFCE7198E80F2EEF3D13000E0149A8283B156EBD69B9426B2F159", HEX_RADIX);
-
- // Identity point in extended representation (0, 1, 1, 0)
- static final BigInteger[] IDENTITY_POINT = new BigInteger[] {ZERO, ONE, ONE, ZERO};
-
- // Helps for reading coordinate type in point representation
- private static final int X = 0;
- private static final int Y = 1;
- private static final int Z = 2;
- private static final int T = 3;
-
- // Number of bits that we need to represent a point. Realistically, we only need 255, but using
- // 256 doesn't hurt.
- private static final int POINT_SIZE_BITS = 256;
-
- /**
- * Returns the result of multiplying point p by scalar k. A point is represented as a BigInteger
- * array of length 2 where the first element (at index 0) is the X coordinate and the second
- * element (at index 1) is the Y coordinate.
- */
- public static BigInteger[] scalarMultiplyAffinePoint(BigInteger[] p, BigInteger k)
- throws Ed25519Exception {
- return toAffine(scalarMultiplyExtendedPoint(toExtended(p), k));
- }
-
- /**
- * Returns the sum of two points in affine representation. A point is represented as a BigInteger
- * array of length 2 where the first element (at index 0) is the X coordinate and the second
- * element (at index 1) is the Y coordinate.
- */
- public static BigInteger[] addAffinePoints(BigInteger[] p1, BigInteger[] p2)
- throws Ed25519Exception {
- return toAffine(addExtendedPoints(toExtended(p1), toExtended(p2)));
- }
-
- /**
- * Returns the result of subtracting p2 from p1 (i.e., p1 - p2) in affine representation. A point
- * is represented as a BigInteger array of length 2 where the first element (at index 0) is the X
- * coordinate and the second element (at index 1) is the Y coordinate.
- */
- public static BigInteger[] subtractAffinePoints(BigInteger[] p1, BigInteger[] p2)
- throws Ed25519Exception {
- return toAffine(subtractExtendedPoints(toExtended(p1), toExtended(p2)));
- }
-
- /**
- * Validates that a given point in affine representation is on the curve and is positive.
- * @throws Ed25519Exception if the point does not validate
- */
- public static void validateAffinePoint(BigInteger[] p) throws Ed25519Exception {
- checkPointIsInAffineRepresentation(p);
-
- BigInteger x = p[X];
- BigInteger y = p[Y];
-
- if (x.signum() != 1 || y.signum() != 1) {
- throw new Ed25519Exception("Point encoding must use only positive integers");
- }
-
- if ((x.compareTo(Ed25519_P) >= 0) || (y.compareTo(Ed25519_P) >= 0)) {
- throw new Ed25519Exception("Point lies outside of the expected field");
- }
-
- BigInteger xx = x.multiply(x);
- BigInteger yy = y.multiply(y);
- BigInteger lhs = xx.negate().add(yy).mod(Ed25519_P); // -x*x + y*y
- BigInteger rhs = ONE.add(Ed25519_D.multiply(xx).multiply(yy)).mod(Ed25519_P); // 1 + d*x*x*y*y
-
- if (!lhs.equals(rhs)) {
- throw new Ed25519Exception("Point does not lie on the expected curve");
- }
- }
-
- /**
- * Returns the result of multiplying point p by scalar k
- */
- static BigInteger[] scalarMultiplyExtendedPoint(BigInteger[] p, BigInteger k)
- throws Ed25519Exception {
- checkPointIsInExtendedRepresentation(p);
- if (k == null) {
- throw new Ed25519Exception("Can't multiply point by null");
- }
-
- if (k.bitLength() > POINT_SIZE_BITS) {
- throw new Ed25519Exception(
- "Refuse to multiply point by scalar with more than " + POINT_SIZE_BITS + " bits");
- }
-
- // Perform best effort time-constant accumulation
- BigInteger[] q = IDENTITY_POINT;
- BigInteger[] r = IDENTITY_POINT;
- BigInteger[] doubleAccumulator = p;
- for (int i = 0; i < POINT_SIZE_BITS; i++) {
- if (k.testBit(i)) {
- q = addExtendedPoints(q, doubleAccumulator);
- } else {
- r = addExtendedPoints(q, doubleAccumulator);
- }
- if (i < POINT_SIZE_BITS - 1) {
- doubleAccumulator = doubleExtendedPoint(doubleAccumulator);
- }
- }
-
- // Not needed, but we're just trying to fool the compiler into not optimizing away r
- r = subtractExtendedPoints(r, r);
- q = addExtendedPoints(q, r);
- return q;
- }
-
- /**
- * Returns the doubling of a point in extended representation
- */
- private static BigInteger[] doubleExtendedPoint(BigInteger[] p) throws Ed25519Exception {
- // The Edwards curve is complete, so we can just add a point to itself.
- // Note that the currently best known algorithms for doubling have the same order as addition.
- // https://hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html
- checkPointIsInExtendedRepresentation(p);
-
- BigInteger c = p[T].pow(2).multiply(Ed25519_K);
- BigInteger d = p[Z].pow(2).shiftLeft(1);
- BigInteger e = p[Y].multiply(p[X]).shiftLeft(2);
- BigInteger f = d.subtract(c);
- BigInteger g = d.add(c);
- BigInteger h = p[Y].pow(2).add(p[X].pow(2)).shiftLeft(1);
-
- return new BigInteger[] {
- e.multiply(f).mod(Ed25519_P),
- g.multiply(h).mod(Ed25519_P),
- f.multiply(g).mod(Ed25519_P),
- e.multiply(h).mod(Ed25519_P)
- };
- }
-
- /**
- * Returns the result of subtracting p2 from p1 (p1 - p2)
- */
- static BigInteger[] subtractExtendedPoints(BigInteger[] p1, BigInteger[] p2)
- throws Ed25519Exception {
- checkPointIsInExtendedRepresentation(p1);
- checkPointIsInExtendedRepresentation(p2);
-
- return addExtendedPoints(p1, new BigInteger[] {p2[X].negate(), p2[Y], p2[Z], p2[T].negate()});
- }
-
- /**
- * Returns the sum of two points in extended representation
- * Uses: https://hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html#addition-add-2008-hwcd-3
- */
- static BigInteger[] addExtendedPoints(BigInteger[] p1, BigInteger[] p2)
- throws Ed25519Exception {
- checkPointIsInExtendedRepresentation(p1);
- checkPointIsInExtendedRepresentation(p2);
-
- BigInteger a = p1[Y].subtract(p1[X]).multiply(p2[Y].subtract(p2[X]));
- BigInteger b = p1[Y].add(p1[X]).multiply(p2[Y].add(p2[X]));
- BigInteger c = p1[T].multiply(Ed25519_K).multiply(p2[T]);
- BigInteger d = p1[Z].add(p1[Z]).multiply(p2[Z]);
- BigInteger e = b.subtract(a);
- BigInteger f = d.subtract(c);
- BigInteger g = d.add(c);
- BigInteger h = b.add(a);
-
- return new BigInteger[] {
- e.multiply(f).mod(Ed25519_P),
- g.multiply(h).mod(Ed25519_P),
- f.multiply(g).mod(Ed25519_P),
- e.multiply(h).mod(Ed25519_P)
- };
- }
-
- /** Converts a point in affine representation to extended representation */
- // TODO(b/120887495): This @VisibleForTesting annotation was being ignored by prod code.
- // Please check that removing it is correct, and remove this comment along with it.
- // @VisibleForTesting
- static BigInteger[] toExtended(BigInteger[] p) throws Ed25519Exception {
- checkPointIsInAffineRepresentation(p);
-
- return new BigInteger[] {p[X], p[Y], ONE, p[X].multiply(p[Y]).mod(Ed25519_P)}; // x, y, 1, x*y
- }
-
- /** Converts a point in extended representation to affine representation */
- // TODO(b/120887495): This @VisibleForTesting annotation was being ignored by prod code.
- // Please check that removing it is correct, and remove this comment along with it.
- // @VisibleForTesting
- static BigInteger[] toAffine(BigInteger[] p) throws Ed25519Exception {
- checkPointIsInExtendedRepresentation(p);
-
- return new BigInteger[] {p[X].multiply(p[Z].modInverse(Ed25519_P)).mod(Ed25519_P), // x = X / Z
- p[Y].multiply(p[Z].modInverse(Ed25519_P)).mod(Ed25519_P)}; // y = Y / Z
- }
-
- /**
- * Checks that a given point is in the extended representation
- * @throws Ed25519Exception if the point is not in the extended representation
- */
- @VisibleForTesting
- static void checkPointIsInExtendedRepresentation(BigInteger[] p) throws Ed25519Exception {
- if (p == null || p.length != 4 || p[X] == null || p[Y] == null || p[Z] == null
- || p[T] == null) {
- throw new Ed25519Exception("Point is not in extended representation");
- }
- }
-
- /**
- * Checks that a given point is in the affine representation
- * @throws Ed25519Exception if the point is not in the affine representation
- */
- @VisibleForTesting
- static void checkPointIsInAffineRepresentation(BigInteger[] p) throws Ed25519Exception {
- if (p == null || p.length != 2 || p[X] == null || p[Y] == null) {
- throw new Ed25519Exception("Point is not in affine representation");
- }
- }
-
- /**
- * Represents an unrecoverable error that has occurred while performing a curve operation.
- */
- public static class Ed25519Exception extends Exception {
- public Ed25519Exception(String message) {
- super(message);
- }
-
- public Ed25519Exception(Exception e) {
- super(e);
- }
-
- public Ed25519Exception(String message, Exception e) {
- super(message, e);
- }
- }
-}
diff --git a/src/main/java/com/google/security/cryptauth/lib/securegcm/EnrollmentCryptoOps.java b/src/main/java/com/google/security/cryptauth/lib/securegcm/EnrollmentCryptoOps.java
deleted file mode 100644
index 450c806..0000000
--- a/src/main/java/com/google/security/cryptauth/lib/securegcm/EnrollmentCryptoOps.java
+++ /dev/null
@@ -1,233 +0,0 @@
-// Copyright 2020 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
-//
-// https://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.security.cryptauth.lib.securegcm;
-
-import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.security.annotations.SuppressInsecureCipherModeCheckerPendingReview;
-import com.google.security.cryptauth.lib.securegcm.SecureGcmProto.GcmDeviceInfo;
-import com.google.security.cryptauth.lib.securegcm.SecureGcmProto.GcmMetadata;
-import com.google.security.cryptauth.lib.securegcm.TransportCryptoOps.PayloadType;
-import com.google.security.cryptauth.lib.securemessage.CryptoOps.EncType;
-import com.google.security.cryptauth.lib.securemessage.CryptoOps.SigType;
-import com.google.security.cryptauth.lib.securemessage.PublicKeyProtoUtil;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageBuilder;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageParser;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.HeaderAndBody;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.SecureMessage;
-import java.security.InvalidKeyException;
-import java.security.KeyPair;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.SignatureException;
-import java.security.spec.InvalidKeySpecException;
-import java.util.Arrays;
-import javax.crypto.KeyAgreement;
-import javax.crypto.SecretKey;
-
-/**
- * Utility class for implementing Secure GCM enrollment flows.
- */
-public class EnrollmentCryptoOps {
-
- private EnrollmentCryptoOps() { } // Do not instantiate
-
- /**
- * Type of symmetric key signature to use for the signcrypted "outer layer" message.
- */
- private static final SigType OUTER_SIG_TYPE = SigType.HMAC_SHA256;
-
- /**
- * Type of symmetric key encryption to use for the signcrypted "outer layer" message.
- */
- private static final EncType OUTER_ENC_TYPE = EncType.AES_256_CBC;
-
- /**
- * Type of public key signature to use for the (cleartext) "inner layer" message.
- */
- private static final SigType INNER_SIG_TYPE = SigType.ECDSA_P256_SHA256;
-
- /**
- * Type of public key signature to use for the (cleartext) "inner layer" message on platforms that
- * don't support Elliptic Curve operations (such as old Android versions).
- */
- private static final SigType LEGACY_INNER_SIG_TYPE = SigType.RSA2048_SHA256;
-
- /**
- * Which {@link KeyAgreement} algorithm to use.
- */
- private static final String KA_ALG = "ECDH";
-
- /**
- * Which {@link KeyAgreement} algorithm to use on platforms that don't support Elliptic Curve.
- */
- private static final String LEGACY_KA_ALG = "DH";
-
- /**
- * Used by both the client and server to perform a key exchange.
- *
- * @return a {@link SecretKey} derived from the key exchange
- * @throws InvalidKeyException if either of the input keys is of the wrong type
- */
- @SuppressInsecureCipherModeCheckerPendingReview // b/32143855
- public static SecretKey doKeyAgreement(PrivateKey myKey, PublicKey peerKey)
- throws InvalidKeyException {
- String alg = KA_ALG;
- if (KeyEncoding.isLegacyPrivateKey(myKey)) {
- alg = LEGACY_KA_ALG;
- }
- KeyAgreement agreement;
- try {
- agreement = KeyAgreement.getInstance(alg);
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException(e);
- }
-
- agreement.init(myKey);
- agreement.doPhase(peerKey, true);
- byte[] agreedKey = agreement.generateSecret();
-
- // Derive a 256-bit AES key by using sha256 on the Diffie-Hellman output
- return KeyEncoding.parseMasterKey(sha256(agreedKey));
- }
-
- public static KeyPair generateEnrollmentKeyAgreementKeyPair(boolean isLegacy) {
- if (isLegacy) {
- return PublicKeyProtoUtil.generateDh2048KeyPair();
- }
- return PublicKeyProtoUtil.generateEcP256KeyPair();
- }
-
- /**
- * @return SHA-256 hash of {@code masterKey}
- */
- public static byte[] getMasterKeyHash(SecretKey masterKey) {
- return sha256(masterKey.getEncoded());
- }
-
- /**
- * Used by the client to signcrypt an enrollment request before sending it to the server.
- *
- * <p>Note: You <em>MUST</em> correctly set the value of the {@code device_master_key_hash} on
- * {@code enrollmentInfo} from {@link #getMasterKeyHash(SecretKey)} before calling this method.
- *
- * @param enrollmentInfo the enrollment request to send to the server. You must correctly set
- * the {@code device_master_key_hash} field.
- * @param masterKey the shared key derived from the key agreement
- * @param signingKey the signing key corresponding to the user's {@link PublicKey} being enrolled
- * @return the encrypted enrollment message
- * @throws IllegalArgumentException if {@code enrollmentInfo} doesn't have a valid
- * {@code device_master_key_hash}
- * @throws InvalidKeyException if {@code masterKey} or {@code signingKey} is the wrong type
- */
- public static byte[] encryptEnrollmentMessage(
- GcmDeviceInfo enrollmentInfo, SecretKey masterKey, PrivateKey signingKey)
- throws InvalidKeyException, NoSuchAlgorithmException {
- if ((enrollmentInfo == null) || (masterKey == null) || (signingKey == null)) {
- throw new NullPointerException();
- }
-
- if (!Arrays.equals(enrollmentInfo.getDeviceMasterKeyHash().toByteArray(),
- getMasterKeyHash(masterKey))) {
- throw new IllegalArgumentException("DeviceMasterKeyHash not set correctly");
- }
-
- // First create the inner message, which is basically a self-signed certificate
- SigType sigType =
- KeyEncoding.isLegacyPrivateKey(signingKey) ? LEGACY_INNER_SIG_TYPE : INNER_SIG_TYPE;
- SecureMessage innerMsg = new SecureMessageBuilder()
- .setVerificationKeyId(enrollmentInfo.getUserPublicKey().toByteArray())
- .buildSignedCleartextMessage(signingKey, sigType, enrollmentInfo.toByteArray());
-
- // Next create the outer message, which uses the newly exchanged master key to signcrypt
- SecureMessage outerMsg = new SecureMessageBuilder()
- .setVerificationKeyId(new byte[] {}) // Empty
- .setPublicMetadata(GcmMetadata.newBuilder()
- .setType(PayloadType.ENROLLMENT.getType())
- .setVersion(SecureGcmConstants.SECURE_GCM_VERSION)
- .build()
- .toByteArray())
- .buildSignCryptedMessage(
- masterKey, OUTER_SIG_TYPE, masterKey, OUTER_ENC_TYPE, innerMsg.toByteArray());
- return outerMsg.toByteArray();
- }
-
- /**
- * Used by the server to decrypt the client's enrollment request.
- * @param enrollmentMessage generated by the client's call to
- * {@link #encryptEnrollmentMessage(GcmDeviceInfo, SecretKey, PrivateKey)}
- * @param masterKey the shared key derived from the key agreement
- * @return the client's enrollment request data
- * @throws SignatureException if {@code enrollmentMessage} is malformed or has been tampered with
- * @throws InvalidKeyException if {@code masterKey} is the wrong type
- */
- public static GcmDeviceInfo decryptEnrollmentMessage(
- byte[] enrollmentMessage, SecretKey masterKey, boolean isLegacy)
- throws SignatureException, InvalidKeyException, NoSuchAlgorithmException {
- if ((enrollmentMessage == null) || (masterKey == null)) {
- throw new NullPointerException();
- }
-
- HeaderAndBody outerHeaderAndBody;
- GcmMetadata outerMetadata;
- HeaderAndBody innerHeaderAndBody;
- byte[] encodedUserPublicKey;
- GcmDeviceInfo enrollmentInfo;
- try {
- SecureMessage outerMsg = SecureMessage.parseFrom(enrollmentMessage);
- outerHeaderAndBody = SecureMessageParser.parseSignCryptedMessage(
- outerMsg, masterKey, OUTER_SIG_TYPE, masterKey, OUTER_ENC_TYPE);
- outerMetadata = GcmMetadata.parseFrom(outerHeaderAndBody.getHeader().getPublicMetadata());
-
- SecureMessage innerMsg = SecureMessage.parseFrom(outerHeaderAndBody.getBody());
- encodedUserPublicKey = SecureMessageParser.getUnverifiedHeader(innerMsg)
- .getVerificationKeyId().toByteArray();
- PublicKey userPublicKey = KeyEncoding.parseUserPublicKey(encodedUserPublicKey);
- SigType sigType = isLegacy ? LEGACY_INNER_SIG_TYPE : INNER_SIG_TYPE;
- innerHeaderAndBody = SecureMessageParser.parseSignedCleartextMessage(
- innerMsg, userPublicKey, sigType);
- enrollmentInfo = GcmDeviceInfo.parseFrom(innerHeaderAndBody.getBody());
- } catch (InvalidProtocolBufferException e) {
- throw new SignatureException(e);
- } catch (InvalidKeySpecException e) {
- throw new SignatureException(e);
- }
-
- boolean verified =
- (outerMetadata.getType() == PayloadType.ENROLLMENT.getType())
- && (outerMetadata.getVersion() <= SecureGcmConstants.SECURE_GCM_VERSION)
- && outerHeaderAndBody.getHeader().getVerificationKeyId().isEmpty()
- && innerHeaderAndBody.getHeader().getPublicMetadata().isEmpty()
- // Verify the encoded public key we used matches the encoded public key key being enrolled
- && Arrays.equals(encodedUserPublicKey, enrollmentInfo.getUserPublicKey().toByteArray())
- && Arrays.equals(getMasterKeyHash(masterKey),
- enrollmentInfo.getDeviceMasterKeyHash().toByteArray());
-
- if (verified) {
- return enrollmentInfo;
- }
- throw new SignatureException();
- }
-
- static byte[] sha256(byte[] input) {
- try {
- MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
- return sha256.digest(input);
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException(e); // Shouldn't happen
- }
- }
-}
diff --git a/src/main/java/com/google/security/cryptauth/lib/securegcm/HandshakeException.java b/src/main/java/com/google/security/cryptauth/lib/securegcm/HandshakeException.java
deleted file mode 100644
index b717eb6..0000000
--- a/src/main/java/com/google/security/cryptauth/lib/securegcm/HandshakeException.java
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2020 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
-//
-// https://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.security.cryptauth.lib.securegcm;
-
-/**
- * Represents an unrecoverable error that has occurred during the handshake procedure.
- */
-public class HandshakeException extends Exception {
- public HandshakeException(String message) {
- super(message);
- }
-
- public HandshakeException(Exception e) {
- super(e);
- }
-
- public HandshakeException(String message, Exception e) {
- super(message, e);
- }
-}
diff --git a/src/main/java/com/google/security/cryptauth/lib/securegcm/KeyEncoding.java b/src/main/java/com/google/security/cryptauth/lib/securegcm/KeyEncoding.java
deleted file mode 100644
index 67e4ace..0000000
--- a/src/main/java/com/google/security/cryptauth/lib/securegcm/KeyEncoding.java
+++ /dev/null
@@ -1,180 +0,0 @@
-// Copyright 2020 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
-//
-// https://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.security.cryptauth.lib.securegcm;
-
-import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.security.cryptauth.lib.securemessage.PublicKeyProtoUtil;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.GenericPublicKey;
-import java.security.KeyFactory;
-import java.security.NoSuchAlgorithmException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.interfaces.ECPrivateKey;
-import java.security.interfaces.ECPublicKey;
-import java.security.spec.InvalidKeySpecException;
-import java.security.spec.PKCS8EncodedKeySpec;
-import javax.crypto.SecretKey;
-import javax.crypto.interfaces.DHPrivateKey;
-import javax.crypto.spec.SecretKeySpec;
-
-/**
- * Utility class for encoding and parsing keys used by SecureGcm.
- */
-public class KeyEncoding {
- private KeyEncoding() {} // Do not instantiate
-
- private static boolean simulateLegacyCryptoRequired = false;
-
- /**
- * The JCA algorithm name to use when encoding/decoding symmetric keys.
- */
- static final String SYMMETRIC_KEY_ENCODING_ALG = "AES";
-
- public static byte[] encodeMasterKey(SecretKey masterKey) {
- return masterKey.getEncoded();
- }
-
- public static SecretKey parseMasterKey(byte[] encodedMasterKey) {
- return new SecretKeySpec(encodedMasterKey, SYMMETRIC_KEY_ENCODING_ALG);
- }
-
- public static byte[] encodeUserPublicKey(PublicKey pk) {
- return encodePublicKey(pk);
- }
-
- public static byte[] encodeUserPrivateKey(PrivateKey sk) {
- return sk.getEncoded();
- }
-
- public static byte[] encodeDeviceSyncGroupPublicKey(PublicKey pk) {
- return PublicKeyProtoUtil.encodePaddedEcPublicKey(pk).toByteArray();
- }
-
- public static PrivateKey parseUserPrivateKey(byte[] encodedPrivateKey, boolean isLegacy)
- throws InvalidKeySpecException {
- PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encodedPrivateKey);
- if (isLegacy) {
- return getRsaKeyFactory().generatePrivate(keySpec);
- }
- return getEcKeyFactory().generatePrivate(keySpec);
- }
-
- public static PublicKey parseUserPublicKey(byte[] keyBytes) throws InvalidKeySpecException {
- return parsePublicKey(keyBytes);
- }
-
- public static PublicKey parseDeviceSyncGroupPublicKey(byte[] keyBytes)
- throws InvalidKeySpecException {
- return parsePublicKey(keyBytes);
- }
-
- public static byte[] encodeKeyAgreementPublicKey(PublicKey pk) {
- return encodePublicKey(pk);
- }
-
- public static PublicKey parseKeyAgreementPublicKey(byte[] keyBytes)
- throws InvalidKeySpecException {
- return parsePublicKey(keyBytes);
- }
-
- public static byte[] encodeKeyAgreementPrivateKey(PrivateKey sk) {
- if (isLegacyPrivateKey(sk)) {
- return PublicKeyProtoUtil.encodeDh2048PrivateKey((DHPrivateKey) sk);
- }
- return sk.getEncoded();
- }
-
- public static PrivateKey parseKeyAgreementPrivateKey(byte[] keyBytes, boolean isLegacy)
- throws InvalidKeySpecException {
- if (isLegacy) {
- return PublicKeyProtoUtil.parseDh2048PrivateKey(keyBytes);
- }
- PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
- return getEcKeyFactory().generatePrivate(keySpec);
- }
-
- public static byte[] encodeSigningPublicKey(PublicKey pk) {
- return encodePublicKey(pk);
- }
-
- public static PublicKey parseSigningPublicKey(byte[] keyBytes) throws InvalidKeySpecException {
- return parsePublicKey(keyBytes);
- }
-
- public static byte[] encodeSigningPrivateKey(PrivateKey sk) {
- return sk.getEncoded();
- }
-
- public static PrivateKey parseSigningPrivateKey(byte[] keyBytes) throws InvalidKeySpecException {
- PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
- return getEcKeyFactory().generatePrivate(keySpec);
- }
-
- public static boolean isLegacyPublicKey(PublicKey pk) {
- if (pk instanceof ECPublicKey) {
- return false;
- }
- return true;
- }
-
- public static boolean isLegacyPrivateKey(PrivateKey sk) {
- if (sk instanceof ECPrivateKey) {
- return false;
- }
- return true;
- }
-
- public static boolean isLegacyCryptoRequired() {
- return PublicKeyProtoUtil.isLegacyCryptoRequired() || simulateLegacyCryptoRequired;
- }
-
- /**
- * When testing, use this to force {@link #isLegacyCryptoRequired()} to return {@code true}
- */
- // @VisibleForTesting
- public static void setSimulateLegacyCrypto(boolean forceLegacy) {
- simulateLegacyCryptoRequired = forceLegacy;
- }
-
- private static byte[] encodePublicKey(PublicKey pk) {
- return PublicKeyProtoUtil.encodePublicKey(pk).toByteArray();
- }
-
- private static PublicKey parsePublicKey(byte[] keyBytes) throws InvalidKeySpecException {
- try {
- return PublicKeyProtoUtil.parsePublicKey(GenericPublicKey.parseFrom(keyBytes));
- } catch (InvalidProtocolBufferException e) {
- throw new InvalidKeySpecException("Unable to parse GenericPublicKey", e);
- } catch (IllegalArgumentException e) {
- throw new InvalidKeySpecException("Unable to parse GenericPublicKey", e);
- }
- }
-
- static KeyFactory getEcKeyFactory() {
- try {
- return KeyFactory.getInstance("EC");
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException(e); // No ECDH provider available
- }
- }
-
- static KeyFactory getRsaKeyFactory() {
- try {
- return KeyFactory.getInstance("RSA");
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException(e); // No RSA provider available
- }
- }
-}
diff --git a/src/main/java/com/google/security/cryptauth/lib/securegcm/SecureGcmConstants.java b/src/main/java/com/google/security/cryptauth/lib/securegcm/SecureGcmConstants.java
deleted file mode 100644
index a69431f..0000000
--- a/src/main/java/com/google/security/cryptauth/lib/securegcm/SecureGcmConstants.java
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright 2020 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
-//
-// https://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.security.cryptauth.lib.securegcm;
-
-/**
- * A container for GCM related constants used by SecureGcm channels.
- */
-public final class SecureGcmConstants {
- private SecureGcmConstants() {} // Do not instantiate
-
- public static final int SECURE_GCM_VERSION = 1;
-
- /**
- * The GCM sender identity used by this library (GMSCore).
- */
- public static final String SENDER_ID = "745476177629";
-
- /**
- * The key used for indexing the GCM {@link TransportCryptoOps.Payload} within {@code AppData}.
- */
- public static final String MESSAGE_KEY = "P";
-
- /**
- * The origin that should be use for GCM device enrollments.
- */
- public static final String GOOGLE_ORIGIN = "google.com";
-
- /**
- * The origin that should be use for GCM Legacy android device enrollments.
- */
- public static final String LEGACY_ANDROID_ORIGIN = "c.g.a.gms";
-
- /**
- * The name of the protocol this library speaks.
- */
- public static final String PROTOCOL_TYPE_NAME = "gcmV1";
-}
diff --git a/src/main/java/com/google/security/cryptauth/lib/securegcm/TransportCryptoOps.java b/src/main/java/com/google/security/cryptauth/lib/securegcm/TransportCryptoOps.java
deleted file mode 100644
index b053bf4..0000000
--- a/src/main/java/com/google/security/cryptauth/lib/securegcm/TransportCryptoOps.java
+++ /dev/null
@@ -1,268 +0,0 @@
-// Copyright 2020 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
-//
-// https://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.security.cryptauth.lib.securegcm;
-
-import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.security.cryptauth.lib.securegcm.SecureGcmProto.GcmMetadata;
-import com.google.security.cryptauth.lib.securemessage.CryptoOps.EncType;
-import com.google.security.cryptauth.lib.securemessage.CryptoOps.SigType;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageBuilder;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageParser;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.HeaderAndBody;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.SecureMessage;
-import java.security.InvalidKeyException;
-import java.security.KeyPair;
-import java.security.NoSuchAlgorithmException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.SignatureException;
-import java.security.interfaces.ECPublicKey;
-import java.security.interfaces.RSAPublicKey;
-import javax.crypto.SecretKey;
-
-/**
- * Utility class for implementing a secure transport for GCM messages.
- */
-public class TransportCryptoOps {
- private TransportCryptoOps() {} // Do not instantiate
-
- /**
- * A type safe version of the {@link SecureGcmProto} {@code Type} codes.
- */
- public enum PayloadType {
- ENROLLMENT(SecureGcmProto.Type.ENROLLMENT),
- TICKLE(SecureGcmProto.Type.TICKLE),
- TX_REQUEST(SecureGcmProto.Type.TX_REQUEST),
- TX_REPLY(SecureGcmProto.Type.TX_REPLY),
- TX_SYNC_REQUEST(SecureGcmProto.Type.TX_SYNC_REQUEST),
- TX_SYNC_RESPONSE(SecureGcmProto.Type.TX_SYNC_RESPONSE),
- TX_PING(SecureGcmProto.Type.TX_PING),
- DEVICE_INFO_UPDATE(SecureGcmProto.Type.DEVICE_INFO_UPDATE),
- TX_CANCEL_REQUEST(SecureGcmProto.Type.TX_CANCEL_REQUEST),
- LOGIN_NOTIFICATION(SecureGcmProto.Type.LOGIN_NOTIFICATION),
- PROXIMITYAUTH_PAIRING(SecureGcmProto.Type.PROXIMITYAUTH_PAIRING),
- GCMV1_IDENTITY_ASSERTION(SecureGcmProto.Type.GCMV1_IDENTITY_ASSERTION),
- DEVICE_TO_DEVICE_RESPONDER_HELLO_PAYLOAD(
- SecureGcmProto.Type.DEVICE_TO_DEVICE_RESPONDER_HELLO_PAYLOAD),
- DEVICE_TO_DEVICE_MESSAGE(SecureGcmProto.Type.DEVICE_TO_DEVICE_MESSAGE),
- DEVICE_PROXIMITY_CALLBACK(SecureGcmProto.Type.DEVICE_PROXIMITY_CALLBACK),
- UNLOCK_KEY_SIGNED_CHALLENGE(SecureGcmProto.Type.UNLOCK_KEY_SIGNED_CHALLENGE);
-
- private final SecureGcmProto.Type type;
- PayloadType(SecureGcmProto.Type type) {
- this.type = type;
- }
-
- public SecureGcmProto.Type getType() {
- return this.type;
- }
-
- public static PayloadType valueOf(SecureGcmProto.Type type) {
- return PayloadType.valueOf(type.getNumber());
- }
-
- public static PayloadType valueOf(int type) {
- for (PayloadType payloadType : PayloadType.values()) {
- if (payloadType.getType().getNumber() == type) {
- return payloadType;
- }
- }
- throw new IllegalArgumentException("Unsupported payload type: " + type);
- }
- }
-
- /**
- * Encapsulates a {@link PayloadType} specifier, and a corresponding raw {@code message} payload.
- */
- public static class Payload {
- private final PayloadType payloadType;
- private final byte[] message;
-
- public Payload(PayloadType payloadType, byte[] message) {
- if ((payloadType == null) || (message == null)) {
- throw new NullPointerException();
- }
- this.payloadType = payloadType;
- this.message = message;
- }
-
- public PayloadType getPayloadType() {
- return payloadType;
- }
-
- public byte[] getMessage() {
- return message;
- }
- }
-
- /**
- * Used by the the server-side to send a secure {@link Payload} to the client.
- *
- * @param masterKey used to signcrypt the {@link Payload}
- * @param keyHandle the name by which the client refers to the specified {@code masterKey}
- */
- public static byte[] signcryptServerMessage(
- Payload payload, SecretKey masterKey, byte[] keyHandle)
- throws InvalidKeyException, NoSuchAlgorithmException {
- if ((payload == null) || (masterKey == null) || (keyHandle == null)) {
- throw new NullPointerException();
- }
- return new SecureMessageBuilder()
- .setVerificationKeyId(keyHandle)
- .setPublicMetadata(GcmMetadata.newBuilder()
- .setType(payload.getPayloadType().getType())
- .setVersion(SecureGcmConstants.SECURE_GCM_VERSION)
- .build()
- .toByteArray())
- .buildSignCryptedMessage(
- masterKey,
- SigType.HMAC_SHA256,
- masterKey,
- EncType.AES_256_CBC,
- payload.getMessage())
- .toByteArray();
- }
-
- /**
- * Extracts the {@code keyHandle} from a {@code signcryptedMessage}.
- *
- * @see #signcryptServerMessage(Payload, SecretKey, byte[])
- */
- public static byte[] getKeyHandleFor(byte[] signcryptedServerMessage)
- throws InvalidProtocolBufferException {
- if (signcryptedServerMessage == null) {
- throw new NullPointerException();
- }
- SecureMessage secmsg = SecureMessage.parseFrom(signcryptedServerMessage);
- return SecureMessageParser.getUnverifiedHeader(secmsg).getVerificationKeyId().toByteArray();
- }
-
- /**
- * Used by a client to recover a secure {@link Payload} sent by the server-side.
- *
- * @see #getKeyHandleFor(byte[])
- * @see #signcryptServerMessage(Payload, SecretKey, byte[])
- */
- public static Payload verifydecryptServerMessage(
- byte[] signcryptedServerMessage, SecretKey masterKey)
- throws SignatureException, InvalidKeyException, NoSuchAlgorithmException {
- if ((signcryptedServerMessage == null) || (masterKey == null)) {
- throw new NullPointerException();
- }
- try {
- SecureMessage secmsg = SecureMessage.parseFrom(signcryptedServerMessage);
- HeaderAndBody parsed = SecureMessageParser.parseSignCryptedMessage(
- secmsg,
- masterKey,
- SigType.HMAC_SHA256,
- masterKey,
- EncType.AES_256_CBC);
- GcmMetadata metadata = GcmMetadata.parseFrom(parsed.getHeader().getPublicMetadata());
- if (metadata.getVersion() > SecureGcmConstants.SECURE_GCM_VERSION) {
- throw new SignatureException("Unsupported protocol version");
- }
- return new Payload(PayloadType.valueOf(metadata.getType()), parsed.getBody().toByteArray());
- } catch (InvalidProtocolBufferException | IllegalArgumentException e) {
- throw new SignatureException(e);
- }
- }
-
- /**
- * Used by the the client-side to send a secure {@link Payload} to the client.
- *
- * @param userKeyPair used to sign the {@link Payload}. In particular, the {@link PrivateKey}
- * portion is used for signing, and (the {@link PublicKey} portion is sent to the server.
- * @param masterKey used to encrypt the {@link Payload}
- */
- public static byte[] signcryptClientMessage(
- Payload payload, KeyPair userKeyPair, SecretKey masterKey)
- throws InvalidKeyException, NoSuchAlgorithmException {
- if ((payload == null) || (masterKey == null)) {
- throw new NullPointerException();
- }
-
- PublicKey userPublicKey = userKeyPair.getPublic();
- PrivateKey userPrivateKey = userKeyPair.getPrivate();
-
- return new SecureMessageBuilder()
- .setVerificationKeyId(KeyEncoding.encodeUserPublicKey(userPublicKey))
- .setPublicMetadata(GcmMetadata.newBuilder()
- .setType(payload.getPayloadType().getType())
- .setVersion(SecureGcmConstants.SECURE_GCM_VERSION)
- .build()
- .toByteArray())
- .buildSignCryptedMessage(
- userPrivateKey,
- getSigTypeFor(userPublicKey),
- masterKey,
- EncType.AES_256_CBC,
- payload.getMessage())
- .toByteArray();
- }
-
- /**
- * Used by the server-side to recover a secure {@link Payload} sent by a client.
- *
- * @see #getEncodedUserPublicKeyFor(byte[])
- * @see #signcryptClientMessage(Payload, KeyPair, SecretKey)
- */
- public static Payload verifydecryptClientMessage(
- byte[] signcryptedClientMessage, PublicKey userPublicKey, SecretKey masterKey)
- throws SignatureException, InvalidKeyException, NoSuchAlgorithmException {
- if ((signcryptedClientMessage == null) || (masterKey == null)) {
- throw new NullPointerException();
- }
- try {
- SecureMessage secmsg = SecureMessage.parseFrom(signcryptedClientMessage);
- HeaderAndBody parsed = SecureMessageParser.parseSignCryptedMessage(
- secmsg,
- userPublicKey,
- getSigTypeFor(userPublicKey),
- masterKey,
- EncType.AES_256_CBC);
- GcmMetadata metadata = GcmMetadata.parseFrom(parsed.getHeader().getPublicMetadata());
- if (metadata.getVersion() > SecureGcmConstants.SECURE_GCM_VERSION) {
- throw new SignatureException("Unsupported protocol version");
- }
- return new Payload(PayloadType.valueOf(metadata.getType()), parsed.getBody().toByteArray());
- } catch (InvalidProtocolBufferException | IllegalArgumentException e) {
- throw new SignatureException(e);
- }
- }
-
- /**
- * Extracts an encoded {@code userPublicKey} from a {@code signcryptedClientMessage}.
- *
- * @see #signcryptClientMessage(Payload, KeyPair, SecretKey)
- */
- public static byte[] getEncodedUserPublicKeyFor(byte[] signcryptedClientMessage)
- throws InvalidProtocolBufferException {
- if (signcryptedClientMessage == null) {
- throw new NullPointerException();
- }
- SecureMessage secmsg = SecureMessage.parseFrom(signcryptedClientMessage);
- return SecureMessageParser.getUnverifiedHeader(secmsg).getVerificationKeyId().toByteArray();
- }
-
- private static SigType getSigTypeFor(PublicKey userPublicKey) throws InvalidKeyException {
- if (userPublicKey instanceof ECPublicKey) {
- return SigType.ECDSA_P256_SHA256;
- } else if (userPublicKey instanceof RSAPublicKey) {
- return SigType.RSA2048_SHA256;
- } else {
- throw new InvalidKeyException("Unsupported key type");
- }
- }
-}
diff --git a/src/main/java/com/google/security/cryptauth/lib/securegcm/Ukey2Handshake.java b/src/main/java/com/google/security/cryptauth/lib/securegcm/Ukey2Handshake.java
deleted file mode 100644
index 8e00ea9..0000000
--- a/src/main/java/com/google/security/cryptauth/lib/securegcm/Ukey2Handshake.java
+++ /dev/null
@@ -1,1041 +0,0 @@
-// Copyright 2020 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
-//
-// https://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.security.cryptauth.lib.securegcm;
-
-import com.google.protobuf.ByteString;
-import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.security.cryptauth.lib.securegcm.UkeyProto.Ukey2Alert;
-import com.google.security.cryptauth.lib.securegcm.UkeyProto.Ukey2ClientFinished;
-import com.google.security.cryptauth.lib.securegcm.UkeyProto.Ukey2ClientInit;
-import com.google.security.cryptauth.lib.securegcm.UkeyProto.Ukey2ClientInit.CipherCommitment;
-import com.google.security.cryptauth.lib.securegcm.UkeyProto.Ukey2Message;
-import com.google.security.cryptauth.lib.securegcm.UkeyProto.Ukey2ServerInit;
-import com.google.security.cryptauth.lib.securemessage.CryptoOps;
-import com.google.security.cryptauth.lib.securemessage.PublicKeyProtoUtil;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.GenericPublicKey;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.security.InvalidKeyException;
-import java.security.KeyPair;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.PublicKey;
-import java.security.SecureRandom;
-import java.security.spec.InvalidKeySpecException;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import javax.annotation.Nullable;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.SecretKeySpec;
-
-/**
- * Implements UKEY2 and produces a {@link D2DConnectionContext}.
- *
- * <p>Client Usage:
- * <code>
- * try {
- * Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
- * byte[] handshakeMessage;
- *
- * // Message 1 (Client Init)
- * handshakeMessage = client.getNextHandshakeMessage();
- * sendMessageToServer(handshakeMessage);
- *
- * // Message 2 (Server Init)
- * handshakeMessage = receiveMessageFromServer();
- * client.parseHandshakeMessage(handshakeMessage);
- *
- * // Message 3 (Client Finish)
- * handshakeMessage = client.getNextHandshakeMessage();
- * sendMessageToServer(handshakeMessage);
- *
- * // Get the auth string
- * byte[] clientAuthString = client.getVerificationString(STRING_LENGTH);
- * showStringToUser(clientAuthString);
- *
- * // Using out-of-band channel, verify auth string, then call:
- * client.verifyHandshake();
- *
- * // Make a connection context
- * D2DConnectionContext clientContext = client.toConnectionContext();
- * } catch (AlertException e) {
- * log(e.getMessage);
- * sendMessageToServer(e.getAlertMessageToSend());
- * } catch (HandshakeException e) {
- * log(e);
- * // terminate handshake
- * }
- * </code>
- *
- * <p>Server Usage:
- * <code>
- * try {
- * Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
- * byte[] handshakeMessage;
- *
- * // Message 1 (Client Init)
- * handshakeMessage = receiveMessageFromClient();
- * server.parseHandshakeMessage(handshakeMessage);
- *
- * // Message 2 (Server Init)
- * handshakeMessage = server.getNextHandshakeMessage();
- * sendMessageToServer(handshakeMessage);
- *
- * // Message 3 (Client Finish)
- * handshakeMessage = receiveMessageFromClient();
- * server.parseHandshakeMessage(handshakeMessage);
- *
- * // Get the auth string
- * byte[] serverAuthString = server.getVerificationString(STRING_LENGTH);
- * showStringToUser(serverAuthString);
- *
- * // Using out-of-band channel, verify auth string, then call:
- * server.verifyHandshake();
- *
- * // Make a connection context
- * D2DConnectionContext serverContext = server.toConnectionContext();
- * } catch (AlertException e) {
- * log(e.getMessage);
- * sendMessageToClient(e.getAlertMessageToSend());
- * } catch (HandshakeException e) {
- * log(e);
- * // terminate handshake
- * }
- * </code>
- */
-public class Ukey2Handshake {
-
- /**
- * Creates a {@link Ukey2Handshake} with a particular cipher that can be used by an initiator /
- * client.
- *
- * @throws HandshakeException
- */
- public static Ukey2Handshake forInitiator(HandshakeCipher cipher) throws HandshakeException {
- return new Ukey2Handshake(InternalState.CLIENT_START, cipher);
- }
-
- /**
- * Creates a {@link Ukey2Handshake} with a particular cipher that can be used by an responder /
- * server.
- *
- * @throws HandshakeException
- */
- public static Ukey2Handshake forResponder(HandshakeCipher cipher) throws HandshakeException {
- return new Ukey2Handshake(InternalState.SERVER_START, cipher);
- }
-
- /**
- * Handshake States. Meaning of states:
- * <ul>
- * <li>IN_PROGRESS: The handshake is in progress, caller should use
- * {@link Ukey2Handshake#getNextHandshakeMessage()} and
- * {@link Ukey2Handshake#parseHandshakeMessage(byte[])} to continue the handshake.
- * <li>VERIFICATION_NEEDED: The handshake is complete, but pending verification of the
- * authentication string. Clients should use {@link Ukey2Handshake#getVerificationString(int)} to
- * get the verification string and use out-of-band methods to authenticate the handshake.
- * <li>VERIFICATION_IN_PROGRESS: The handshake is complete, verification string has been
- * generated, but has not been confirmed. After authenticating the handshake out-of-band, use
- * {@link Ukey2Handshake#verifyHandshake()} to mark the handshake as verified.
- * <li>FINISHED: The handshake is finished, and caller can use
- * {@link Ukey2Handshake#toConnectionContext()} to produce a {@link D2DConnectionContext}.
- * <li>ALREADY_USED: The handshake has already been used and should be discarded / garbage
- * collected.
- * <li>ERROR: The handshake produced an error and should be destroyed.
- * </ul>
- */
- public enum State {
- IN_PROGRESS,
- VERIFICATION_NEEDED,
- VERIFICATION_IN_PROGRESS,
- FINISHED,
- ALREADY_USED,
- ERROR,
- }
-
- /**
- * Currently implemented UKEY2 handshake ciphers. Each cipher is a tuple consisting of a key
- * negotiation cipher and a hash function used for a commitment. Currently the ciphers are:
- * <code>
- * +-----------------------------------------------------+
- * | Enum | Key negotiation | Hash function |
- * +-------------+-----------------------+---------------+
- * | P256_SHA512 | ECDH using NIST P-256 | SHA512 |
- * +-----------------------------------------------------+
- * </code>
- *
- * <p>Note that these should correspond to values in device_to_device_messages.proto.
- */
- public enum HandshakeCipher {
- P256_SHA512(UkeyProto.Ukey2HandshakeCipher.P256_SHA512);
- // TODO(aczeskis): add CURVE25519_SHA512
-
- private final UkeyProto.Ukey2HandshakeCipher value;
-
- HandshakeCipher(UkeyProto.Ukey2HandshakeCipher value) {
- // Make sure we only accept values that are valid as per the ukey protobuf.
- // NOTE: Don't use switch statement on value, as that will trigger a bug. b/30682989.
- if (value == UkeyProto.Ukey2HandshakeCipher.P256_SHA512) {
- this.value = value;
- } else {
- throw new IllegalArgumentException("Unknown cipher value: " + value);
- }
- }
-
- public UkeyProto.Ukey2HandshakeCipher getValue() {
- return value;
- }
- }
-
- /**
- * If thrown, this exception contains information that should be sent on the wire. Specifically,
- * the {@link #getAlertMessageToSend()} method returns a <code>byte[]</code> that communicates the
- * error to the other party in the handshake. Meanwhile, the {@link #getMessage()} method can be
- * used to get a log-able error message.
- */
- public static class AlertException extends Exception {
- private final Ukey2Alert alertMessageToSend;
-
- public AlertException(String alertMessageToLog, Ukey2Alert alertMessageToSend) {
- super(alertMessageToLog);
- this.alertMessageToSend = alertMessageToSend;
- }
-
- /**
- * @return a message suitable for sending to other member of handshake.
- */
- public byte[] getAlertMessageToSend() {
- return alertMessageToSend.toByteArray();
- }
- }
-
- // Maximum version of the handshake supported by this class.
- public static final int VERSION = 1;
-
- // Random nonce is fixed at 32 bytes (as per go/ukey2).
- private static final int NONCE_LENGTH_IN_BYTES = 32;
-
- private static final String UTF_8 = "UTF-8";
-
- // Currently, we only support one next protocol.
- private static final String NEXT_PROTOCOL = "AES_256_CBC-HMAC_SHA256";
-
- // Clients need to store a map of message 3's (client finishes) for each commitment.
- private final HashMap<HandshakeCipher, byte[]> rawMessage3Map = new HashMap<>();
-
- private final HandshakeCipher handshakeCipher;
- private final HandshakeRole handshakeRole;
- private InternalState handshakeState;
- private final KeyPair ourKeyPair;
- private PublicKey theirPublicKey;
- private SecretKey derivedSecretKey;
-
- // Servers need to store client commitments.
- private byte[] theirCommitment;
-
- // We store the raw messages sent for computing the authentication strings and next key.
- private byte[] rawMessage1;
- private byte[] rawMessage2;
-
- // Enums for internal state machinery
- private enum InternalState {
- // Initiator/client state
- CLIENT_START,
- CLIENT_WAITING_FOR_SERVER_INIT,
- CLIENT_AFTER_SERVER_INIT,
-
- // Responder/server state
- SERVER_START,
- SERVER_AFTER_CLIENT_INIT,
- SERVER_WAITING_FOR_CLIENT_FINISHED,
-
- // Common completion state
- HANDSHAKE_VERIFICATION_NEEDED,
- HANDSHAKE_VERIFICATION_IN_PROGRESS,
- HANDSHAKE_FINISHED,
- HANDSHAKE_ALREADY_USED,
- HANDSHAKE_ERROR,
- }
-
- // Helps us remember our role in the handshake
- private enum HandshakeRole {
- CLIENT,
- SERVER
- }
-
- /**
- * Never invoked directly. Caller should use {@link #forInitiator(HandshakeCipher)} or
- * {@link #forResponder(HandshakeCipher)} instead.
- *
- * @throws HandshakeException if an unrecoverable error occurs and the connection should be shut
- * down.
- */
- private Ukey2Handshake(InternalState state, HandshakeCipher cipher) throws HandshakeException {
- if (cipher == null) {
- throwIllegalArgumentException("Invalid handshake cipher");
- }
- this.handshakeCipher = cipher;
-
- switch (state) {
- case CLIENT_START:
- handshakeRole = HandshakeRole.CLIENT;
- break;
- case SERVER_START:
- handshakeRole = HandshakeRole.SERVER;
- break;
- default:
- throwIllegalStateException("Invalid handshake state");
- handshakeRole = null; // unreachable, but makes compiler happy
- }
- this.handshakeState = state;
-
- this.ourKeyPair = genKeyPair(cipher);
- }
-
- /**
- * Get the next handshake message suitable for sending on the wire.
- *
- * @throws HandshakeException if an unrecoverable error occurs and the connection should be shut
- * down.
- */
- public byte[] getNextHandshakeMessage() throws HandshakeException {
- switch (handshakeState) {
- case CLIENT_START:
- rawMessage1 = makeUkey2Message(Ukey2Message.Type.CLIENT_INIT, makeClientInitMessage());
- handshakeState = InternalState.CLIENT_WAITING_FOR_SERVER_INIT;
- return rawMessage1;
-
- case SERVER_AFTER_CLIENT_INIT:
- rawMessage2 = makeUkey2Message(Ukey2Message.Type.SERVER_INIT, makeServerInitMessage());
- handshakeState = InternalState.SERVER_WAITING_FOR_CLIENT_FINISHED;
- return rawMessage2;
-
- case CLIENT_AFTER_SERVER_INIT:
- // Make sure we have a message 3 for the chosen cipher.
- if (!rawMessage3Map.containsKey(handshakeCipher)) {
- throwIllegalStateException(
- "Client state is CLIENT_AFTER_SERVER_INIT, and cipher is "
- + handshakeCipher
- + ", but no corresponding raw client finished message has been generated");
- }
- handshakeState = InternalState.HANDSHAKE_VERIFICATION_NEEDED;
- return rawMessage3Map.get(handshakeCipher);
-
- default:
- throwIllegalStateException("Cannot get next message in state: " + handshakeState);
- return null; // unreachable, but makes compiler happy
- }
- }
-
- /**
- * Returns an authentication string suitable for authenticating the handshake out-of-band. Note
- * that the authentication string can be short (e.g., a 6 digit visual confirmation code). Note:
- * this should only be called when the state returned byte {@link #getHandshakeState()} is
- * {@link State#VERIFICATION_NEEDED}, which means this can only be called once.
- *
- * @param byteLength length of output in bytes. Min length is 1; max length is 32.
- */
- public byte[] getVerificationString(int byteLength) throws HandshakeException {
- if (byteLength < 1 || byteLength > 32) {
- throwIllegalArgumentException("Minimum length is 1 byte, max is 32 bytes");
- }
-
- if (handshakeState != InternalState.HANDSHAKE_VERIFICATION_NEEDED) {
- throwIllegalStateException("Unexpected state: " + handshakeState);
- }
-
- try {
- derivedSecretKey =
- EnrollmentCryptoOps.doKeyAgreement(ourKeyPair.getPrivate(), theirPublicKey);
- } catch (InvalidKeyException e) {
- // unreachable in practice
- throwHandshakeException(e);
- }
-
- ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
- try {
- byteStream.write(rawMessage1);
- byteStream.write(rawMessage2);
- } catch (IOException e) {
- // unreachable in practice
- throwHandshakeException(e);
- }
- byte[] info = byteStream.toByteArray();
-
- byte[] salt = null;
-
- try {
- salt = "UKEY2 v1 auth".getBytes(UTF_8);
- } catch (UnsupportedEncodingException e) {
- // unreachable in practice
- throwHandshakeException(e);
- }
-
- byte[] authString = null;
- try {
- authString = CryptoOps.hkdf(derivedSecretKey, salt, info);
- } catch (InvalidKeyException | NoSuchAlgorithmException e) {
- // unreachable in practice
- throwHandshakeException(e);
- }
-
- handshakeState = InternalState.HANDSHAKE_VERIFICATION_IN_PROGRESS;
- return Arrays.copyOf(authString, byteLength);
- }
-
- /**
- * Invoked to let handshake state machine know that caller has validated the authentication
- * string obtained via {@link #getVerificationString(int)}; Note: this should only be called when
- * the state returned byte {@link #getHandshakeState()} is {@link State#VERIFICATION_IN_PROGRESS}.
- */
- public void verifyHandshake() {
- if (handshakeState != InternalState.HANDSHAKE_VERIFICATION_IN_PROGRESS) {
- throwIllegalStateException("Unexpected state: " + handshakeState);
- }
- handshakeState = InternalState.HANDSHAKE_FINISHED;
- }
-
- /**
- * Parses the given handshake message.
- * @throws AlertException if an error occurs that should be sent to other party.
- * @throws HandshakeException in an error occurs and the connection should be torn down.
- */
- public void parseHandshakeMessage(byte[] handshakeMessage)
- throws AlertException, HandshakeException {
- switch (handshakeState) {
- case SERVER_START:
- parseMessage1(handshakeMessage);
- handshakeState = InternalState.SERVER_AFTER_CLIENT_INIT;
- break;
-
- case CLIENT_WAITING_FOR_SERVER_INIT:
- parseMessage2(handshakeMessage);
- handshakeState = InternalState.CLIENT_AFTER_SERVER_INIT;
- break;
-
- case SERVER_WAITING_FOR_CLIENT_FINISHED:
- parseMessage3(handshakeMessage);
- handshakeState = InternalState.HANDSHAKE_VERIFICATION_NEEDED;
- break;
-
- default:
- throwIllegalStateException("Cannot parse message in state " + handshakeState);
- }
- }
-
- /**
- * Returns the current state of the handshake. See {@link State}.
- */
- public State getHandshakeState() {
- switch (handshakeState) {
- case CLIENT_START:
- case CLIENT_WAITING_FOR_SERVER_INIT:
- case CLIENT_AFTER_SERVER_INIT:
- case SERVER_START:
- case SERVER_WAITING_FOR_CLIENT_FINISHED:
- case SERVER_AFTER_CLIENT_INIT:
- // fallback intended -- these are all in-progress states
- return State.IN_PROGRESS;
-
- case HANDSHAKE_ERROR:
- return State.ERROR;
-
- case HANDSHAKE_VERIFICATION_NEEDED:
- return State.VERIFICATION_NEEDED;
-
- case HANDSHAKE_VERIFICATION_IN_PROGRESS:
- return State.VERIFICATION_IN_PROGRESS;
-
- case HANDSHAKE_FINISHED:
- return State.FINISHED;
-
- case HANDSHAKE_ALREADY_USED:
- return State.ALREADY_USED;
-
- default:
- // unreachable in practice
- throwIllegalStateException("Unknown state");
- return null; // really unreachable, but makes compiler happy
- }
- }
-
- /**
- * Can be called to generate a {@link D2DConnectionContext}. Note: this should only be called
- * when the state returned byte {@link #getHandshakeState()} is {@link State#FINISHED}.
- *
- * @throws HandshakeException
- */
- public D2DConnectionContext toConnectionContext() throws HandshakeException {
- switch (handshakeState) {
- case HANDSHAKE_ERROR:
- throwIllegalStateException("Cannot make context; handshake had error");
- return null; // makes linter happy
- case HANDSHAKE_ALREADY_USED:
- throwIllegalStateException("Cannot reuse handshake context; is has already been used");
- return null; // makes linter happy
- case HANDSHAKE_VERIFICATION_NEEDED:
- throwIllegalStateException("Handshake not verified, cannot create context");
- return null; // makes linter happy
- case HANDSHAKE_FINISHED:
- // We're done, okay to return a context
- break;
- default:
- // unreachable in practice
- throwIllegalStateException("Handshake is not complete; cannot create connection context");
- }
-
- if (derivedSecretKey == null) {
- throwIllegalStateException("Unexpected state error: derived key is null");
- }
-
- ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
- try {
- byteStream.write(rawMessage1);
- byteStream.write(rawMessage2);
- } catch (IOException e) {
- // unreachable in practice
- throwHandshakeException(e);
- }
- byte[] info = byteStream.toByteArray();
-
- byte[] salt = null;
- try {
- salt = "UKEY2 v1 next".getBytes(UTF_8);
- } catch (UnsupportedEncodingException e) {
- // unreachable
- throwHandshakeException(e);
- }
-
- SecretKey nextProtocolKey = null;
- try {
- nextProtocolKey = new SecretKeySpec(CryptoOps.hkdf(derivedSecretKey, salt, info), "AES");
- } catch (InvalidKeyException | NoSuchAlgorithmException e) {
- // unreachable in practice
- throwHandshakeException(e);
- }
-
- SecretKey clientKey = null;
- SecretKey serverKey = null;
- try {
- clientKey = D2DCryptoOps.deriveNewKeyForPurpose(nextProtocolKey, "client");
- serverKey = D2DCryptoOps.deriveNewKeyForPurpose(nextProtocolKey, "server");
- } catch (InvalidKeyException | NoSuchAlgorithmException e) {
- // unreachable in practice
- throwHandshakeException(e);
- }
-
- handshakeState = InternalState.HANDSHAKE_ALREADY_USED;
-
- return new D2DConnectionContextV1(
- handshakeRole == HandshakeRole.CLIENT ? clientKey : serverKey,
- handshakeRole == HandshakeRole.CLIENT ? serverKey : clientKey,
- 0 /* initial encode sequence number */,
- 0 /* initial decode sequence number */);
- }
-
- /**
- * Generates the byte[] encoding of a {@link Ukey2ClientInit} message.
- *
- * @throws HandshakeException
- */
- private byte[] makeClientInitMessage() throws HandshakeException {
- Ukey2ClientInit.Builder clientInit = Ukey2ClientInit.newBuilder();
- clientInit.setVersion(VERSION);
- clientInit.setRandom(ByteString.copyFrom(generateRandomNonce()));
- clientInit.setNextProtocol(NEXT_PROTOCOL);
-
- // At the moment, we only support one cipher
- clientInit.addCipherCommitments(generateP256SHA512Commitment());
-
- return clientInit.build().toByteArray();
- }
-
- /**
- * Generates the byte[] encoding of a {@link Ukey2ServerInit} message.
- */
- private byte[] makeServerInitMessage() {
- Ukey2ServerInit.Builder serverInit = Ukey2ServerInit.newBuilder();
- serverInit.setVersion(VERSION);
- serverInit.setRandom(ByteString.copyFrom(generateRandomNonce()));
- serverInit.setHandshakeCipher(handshakeCipher.getValue());
- serverInit.setPublicKey(
- PublicKeyProtoUtil.encodePublicKey(ourKeyPair.getPublic()).toByteString());
-
- return serverInit.build().toByteArray();
- }
-
- /**
- * Generates a keypair for the provided handshake cipher. Currently only P256_SHA512 is
- * supported.
- *
- * @throws HandshakeException
- */
- private KeyPair genKeyPair(HandshakeCipher cipher) throws HandshakeException {
- switch (cipher) {
- case P256_SHA512:
- return PublicKeyProtoUtil.generateEcP256KeyPair();
- default:
- // Should never happen
- throwHandshakeException("unknown cipher: " + cipher);
- }
- return null; // unreachable, but makes compiler happy
- }
-
- /**
- * Attempts to parse message 1 (which is a wrapped {@link Ukey2ClientInit}). See go/ukey2 for
- * details.
- *
- * @throws AlertException if an error occurs
- */
- private void parseMessage1(byte[] handshakeMessage) throws AlertException, HandshakeException {
- // Deserialize the protobuf; send a BAD_MESSAGE message if deserialization fails
- Ukey2Message message = null;
- try {
- message = Ukey2Message.parseFrom(handshakeMessage);
- } catch (InvalidProtocolBufferException e) {
- throwAlertException(Ukey2Alert.AlertType.BAD_MESSAGE,
- "Can't parse message 1 " + e.getMessage());
- }
-
- // Verify that message_type == Type.CLIENT_INIT; send a BAD_MESSAGE_TYPE message if mismatch
- if (!message.hasMessageType() || message.getMessageType() != Ukey2Message.Type.CLIENT_INIT) {
- throwAlertException(
- Ukey2Alert.AlertType.BAD_MESSAGE_TYPE,
- "Expected, but did not find ClientInit message type");
- }
-
- // Deserialize message_data as a ClientInit message; send a BAD_MESSAGE_DATA message if
- // deserialization fails
- if (!message.hasMessageData()) {
- throwAlertException(Ukey2Alert.AlertType.BAD_MESSAGE_DATA,
- "Expected message data, but didn't find it");
- }
- Ukey2ClientInit clientInit = null;
- try {
- clientInit = Ukey2ClientInit.parseFrom(message.getMessageData());
- } catch (InvalidProtocolBufferException e) {
- throwAlertException(Ukey2Alert.AlertType.BAD_MESSAGE_DATA,
- "Can't parse message data into ClientInit");
- }
-
- // Check that version == VERSION; send BAD_VERSION message if mismatch
- if (!clientInit.hasVersion()) {
- throwAlertException(Ukey2Alert.AlertType.BAD_VERSION, "ClientInit missing version");
- }
- if (clientInit.getVersion() != VERSION) {
- throwAlertException(Ukey2Alert.AlertType.BAD_VERSION, "ClientInit version mismatch");
- }
-
- // Check that random is exactly NONCE_LENGTH_IN_BYTES bytes; send Alert.BAD_RANDOM message if
- // not.
- if (!clientInit.hasRandom()) {
- throwAlertException(Ukey2Alert.AlertType.BAD_RANDOM, "ClientInit missing random");
- }
- if (clientInit.getRandom().toByteArray().length != NONCE_LENGTH_IN_BYTES) {
- throwAlertException(Ukey2Alert.AlertType.BAD_RANDOM, "ClientInit has incorrect nonce length");
- }
-
- // Check to see if any of the handshake_cipher in cipher_commitment are acceptable. Servers
- // should select the first handshake_cipher that it finds acceptable to support clients
- // signaling deprecated but supported HandshakeCiphers. If no handshake_cipher is acceptable
- // (or there are no HandshakeCiphers in the message), the server sends a BAD_HANDSHAKE_CIPHER
- // message
- List<Ukey2ClientInit.CipherCommitment> commitments = clientInit.getCipherCommitmentsList();
- if (commitments.isEmpty()) {
- throwAlertException(
- Ukey2Alert.AlertType.BAD_HANDSHAKE_CIPHER, "ClientInit is missing cipher commitments");
- }
- for (Ukey2ClientInit.CipherCommitment commitment : commitments) {
- if (!commitment.hasHandshakeCipher()
- || !commitment.hasCommitment()) {
- throwAlertException(
- Ukey2Alert.AlertType.BAD_HANDSHAKE_CIPHER,
- "ClientInit has improperly formatted cipher commitment");
- }
-
- // TODO(aczeskis): for now we only support one cipher, eventually support more
- if (commitment.getHandshakeCipher() == handshakeCipher.getValue()) {
- theirCommitment = commitment.getCommitment().toByteArray();
- }
- }
- if (theirCommitment == null) {
- throwAlertException(Ukey2Alert.AlertType.BAD_HANDSHAKE_CIPHER,
- "No acceptable commitments found");
- }
-
- // Checks that next_protocol contains a protocol that the server supports. Send a
- // BAD_NEXT_PROTOCOL message if not. We currently only support one protocol
- if (!clientInit.hasNextProtocol() || !NEXT_PROTOCOL.equals(clientInit.getNextProtocol())) {
- throwAlertException(Ukey2Alert.AlertType.BAD_NEXT_PROTOCOL, "Incorrect next protocol");
- }
-
- // Store raw message for AUTH_STRING computation
- rawMessage1 = handshakeMessage;
- }
-
- /**
- * Attempts to parse message 2 (which is a wrapped {@link Ukey2ServerInit}). See go/ukey2 for
- * details.
- */
- private void parseMessage2(final byte[] handshakeMessage)
- throws AlertException, HandshakeException {
- // Deserialize the protobuf; send a BAD_MESSAGE message if deserialization fails
- Ukey2Message message = null;
- try {
- message = Ukey2Message.parseFrom(handshakeMessage);
- } catch (InvalidProtocolBufferException e) {
- throwAlertException(Ukey2Alert.AlertType.BAD_MESSAGE,
- "Can't parse message 2 " + e.getMessage());
- }
-
- // Verify that message_type == Type.SERVER_INIT; send a BAD_MESSAGE_TYPE message if mismatch
- if (!message.hasMessageType()) {
- throwAlertException(Ukey2Alert.AlertType.BAD_MESSAGE_TYPE,
- "Expected, but did not find message type");
- }
- if (message.getMessageType() == Ukey2Message.Type.ALERT) {
- handshakeState = InternalState.HANDSHAKE_ERROR;
- throwHandshakeMessageFromAlertMessage(message);
- }
- if (message.getMessageType() != Ukey2Message.Type.SERVER_INIT) {
- throwAlertException(
- Ukey2Alert.AlertType.BAD_MESSAGE_TYPE,
- "Expected, but did not find SERVER_INIT message type");
- }
-
- // Deserialize message_data as a ServerInit message; send a BAD_MESSAGE_DATA message if
- // deserialization fails
- if (!message.hasMessageData()) {
-
- throwAlertException(Ukey2Alert.AlertType.BAD_MESSAGE_DATA,
- "Expected message data, but didn't find it");
- }
- Ukey2ServerInit serverInit = null;
- try {
- serverInit = Ukey2ServerInit.parseFrom(message.getMessageData());
- } catch (InvalidProtocolBufferException e) {
- throwAlertException(Ukey2Alert.AlertType.BAD_MESSAGE_DATA,
- "Can't parse message data into ServerInit");
- }
-
- // Check that version == VERSION; send BAD_VERSION message if mismatch
- if (!serverInit.hasVersion()) {
- throwAlertException(Ukey2Alert.AlertType.BAD_VERSION, "ServerInit missing version");
- }
- if (serverInit.getVersion() != VERSION) {
- throwAlertException(Ukey2Alert.AlertType.BAD_VERSION, "ServerInit version mismatch");
- }
-
- // Check that random is exactly NONCE_LENGTH_IN_BYTES bytes; send Alert.BAD_RANDOM message if
- // not.
- if (!serverInit.hasRandom()) {
- throwAlertException(Ukey2Alert.AlertType.BAD_RANDOM, "ServerInit missing random");
- }
- if (serverInit.getRandom().toByteArray().length != NONCE_LENGTH_IN_BYTES) {
- throwAlertException(Ukey2Alert.AlertType.BAD_RANDOM, "ServerInit has incorrect nonce length");
- }
-
- // Check that handshake_cipher matches a handshake cipher that was sent in
- // ClientInit.cipher_commitments. If not, send a BAD_HANDSHAKECIPHER message
- if (!serverInit.hasHandshakeCipher()) {
- throwAlertException(Ukey2Alert.AlertType.BAD_HANDSHAKE_CIPHER, "No handshake cipher found");
- }
- HandshakeCipher serverCipher = null;
- for (HandshakeCipher cipher : HandshakeCipher.values()) {
- if (cipher.getValue() == serverInit.getHandshakeCipher()) {
- serverCipher = cipher;
- break;
- }
- }
- if (serverCipher == null || serverCipher != handshakeCipher) {
- throwAlertException(Ukey2Alert.AlertType.BAD_HANDSHAKE_CIPHER,
- "No acceptable handshake cipher found");
- }
-
- // Check that public_key parses into a correct public key structure. If not, send a
- // BAD_PUBLIC_KEY message.
- if (!serverInit.hasPublicKey()) {
- throwAlertException(Ukey2Alert.AlertType.BAD_PUBLIC_KEY, "No public key found in ServerInit");
- }
- theirPublicKey = parseP256PublicKey(serverInit.getPublicKey().toByteArray());
-
- // Store raw message for AUTH_STRING computation
- rawMessage2 = handshakeMessage;
- }
-
- /**
- * Attempts to parse message 3 (which is a wrapped {@link Ukey2ClientFinished}). See go/ukey2 for
- * details.
- */
- private void parseMessage3(final byte[] handshakeMessage) throws HandshakeException {
- // Deserialize the protobuf; terminate the connection if deserialization fails.
- Ukey2Message message = null;
- try {
- message = Ukey2Message.parseFrom(handshakeMessage);
- } catch (InvalidProtocolBufferException e) {
- throwHandshakeException("Can't parse message 3", e);
- }
-
- // Verify that message_type == Type.CLIENT_FINISH; terminate connection if mismatch occurs
- if (!message.hasMessageType()) {
- throw new HandshakeException("Expected, but did not find message type");
- }
- if (message.getMessageType() == Ukey2Message.Type.ALERT) {
- throwHandshakeMessageFromAlertMessage(message);
- }
- if (message.getMessageType() != Ukey2Message.Type.CLIENT_FINISH) {
- throwHandshakeException("Expected, but did not find CLIENT_FINISH message type");
- }
-
- // Verify that the hash of the ClientFinished matches the expected commitment from ClientInit.
- // Terminate the connection if the expected match fails.
- verifyCommitment(handshakeMessage);
-
- // Deserialize message_data as a ClientFinished message; terminate the connection if
- // deserialization fails.
- if (!message.hasMessageData()) {
- throwHandshakeException("Expected message data, but didn't find it");
- }
- Ukey2ClientFinished clientFinished = null;
- try {
- clientFinished = Ukey2ClientFinished.parseFrom(message.getMessageData());
- } catch (InvalidProtocolBufferException e) {
- throwHandshakeException(e);
- }
-
- // Check that public_key parses into a correct public key structure. If not, terminate the
- // connection.
- if (!clientFinished.hasPublicKey()) {
- throwHandshakeException("No public key found in ClientFinished");
- }
- try {
- theirPublicKey = parseP256PublicKey(clientFinished.getPublicKey().toByteArray());
- } catch (AlertException e) {
- // Wrap in a HandshakeException because error should not be sent on the wire.
- throwHandshakeException(e);
- }
- }
-
- private void verifyCommitment(byte[] handshakeMessage) throws HandshakeException {
- byte[] actualClientFinishHash = null;
- switch (handshakeCipher) {
- case P256_SHA512:
- actualClientFinishHash = sha512(handshakeMessage);
- break;
- default:
- // should be unreachable
- throwIllegalStateException("Unexpected handshakeCipher");
- }
-
- // Time constant after Java SE 6 Update 17
- // See http://www.oracle.com/technetwork/java/javase/6u17-141447.html
- if (!MessageDigest.isEqual(actualClientFinishHash, theirCommitment)) {
- throwHandshakeException("Commitment does not match");
- }
- }
-
- private void throwHandshakeMessageFromAlertMessage(Ukey2Message message)
- throws HandshakeException {
- if (message.hasMessageData()) {
- Ukey2Alert alert = null;
- try {
- alert = Ukey2Alert.parseFrom(message.getMessageData());
- } catch (InvalidProtocolBufferException e) {
- throwHandshakeException("Cannot parse alert message", e);
- }
-
- if (alert.hasType() && alert.hasErrorMessage()) {
- throwHandshakeException(
- "Received Alert message. Type: "
- + alert.getType()
- + " Error Message: "
- + alert.getErrorMessage());
- } else if (alert.hasType()) {
- throwHandshakeException("Received Alert message. Type: " + alert.getType());
- }
- }
-
- throwHandshakeException("Received empty Alert Message");
- }
-
- /**
- * Parses an encoded public P256 key.
- */
- private PublicKey parseP256PublicKey(byte[] encodedPublicKey)
- throws AlertException, HandshakeException {
- try {
- return PublicKeyProtoUtil.parsePublicKey(GenericPublicKey.parseFrom(encodedPublicKey));
- } catch (InvalidProtocolBufferException | InvalidKeySpecException e) {
- throwAlertException(Ukey2Alert.AlertType.BAD_PUBLIC_KEY,
- "Cannot parse public key: " + e.getMessage());
- return null; // unreachable, but makes compiler happy
- }
- }
-
- /**
- * Generates a {@link CipherCommitment} for the P256_SHA512 cipher.
- */
- private CipherCommitment generateP256SHA512Commitment() throws HandshakeException {
- // Generate the corresponding finished message if it's not done yet
- if (!rawMessage3Map.containsKey(HandshakeCipher.P256_SHA512)) {
- generateP256SHA512ClientFinished(ourKeyPair);
- }
-
- CipherCommitment.Builder cipherCommitment = CipherCommitment.newBuilder();
- cipherCommitment.setHandshakeCipher(UkeyProto.Ukey2HandshakeCipher.P256_SHA512);
- cipherCommitment.setCommitment(
- ByteString.copyFrom(sha512(rawMessage3Map.get(HandshakeCipher.P256_SHA512))));
-
- return cipherCommitment.build();
- }
-
- /**
- * Generates and records a {@link Ukey2ClientFinished} message for the P256_SHA512 cipher.
- */
- private Ukey2ClientFinished generateP256SHA512ClientFinished(KeyPair p256KeyPair) {
- byte[] encodedKey = PublicKeyProtoUtil.encodePublicKey(p256KeyPair.getPublic()).toByteArray();
-
- Ukey2ClientFinished.Builder clientFinished = Ukey2ClientFinished.newBuilder();
- clientFinished.setPublicKey(ByteString.copyFrom(encodedKey));
-
- rawMessage3Map.put(
- HandshakeCipher.P256_SHA512,
- makeUkey2Message(Ukey2Message.Type.CLIENT_FINISH, clientFinished.build().toByteArray()));
-
- return clientFinished.build();
- }
-
- /**
- * Generates the serialized representation of a {@link Ukey2Message} based on the provided type
- * and data.
- */
- private byte[] makeUkey2Message(Ukey2Message.Type messageType, byte[] messageData) {
- Ukey2Message.Builder message = Ukey2Message.newBuilder();
-
- switch (messageType) {
- case ALERT:
- case CLIENT_INIT:
- case SERVER_INIT:
- case CLIENT_FINISH:
- // fall through intentional; valid message types
- break;
- default:
- throwIllegalArgumentException("Invalid message type: " + messageType);
- }
- message.setMessageType(messageType);
-
- // Alerts a blank message data field
- if (messageType != Ukey2Message.Type.ALERT) {
- if (messageData == null || messageData.length == 0) {
- throwIllegalArgumentException("Cannot send empty message data for non-alert messages");
- }
- message.setMessageData(ByteString.copyFrom(messageData));
- }
-
- return message.build().toByteArray();
- }
-
- /**
- * Returns a {@link Ukey2Alert} message of given type and having the loggable additional data if
- * present.
- */
- private Ukey2Alert makeAlertMessage(Ukey2Alert.AlertType alertType,
- @Nullable String loggableAdditionalData) throws HandshakeException {
- switch (alertType) {
- case BAD_MESSAGE:
- case BAD_MESSAGE_TYPE:
- case INCORRECT_MESSAGE:
- case BAD_MESSAGE_DATA:
- case BAD_VERSION:
- case BAD_RANDOM:
- case BAD_HANDSHAKE_CIPHER:
- case BAD_NEXT_PROTOCOL:
- case BAD_PUBLIC_KEY:
- case INTERNAL_ERROR:
- // fall through intentional; valid alert types
- break;
- default:
- throwHandshakeException("Unknown alert type: " + alertType);
- }
-
- Ukey2Alert.Builder alert = Ukey2Alert.newBuilder();
- alert.setType(alertType);
-
- if (loggableAdditionalData != null) {
- alert.setErrorMessage(loggableAdditionalData);
- }
-
- return alert.build();
- }
-
- /**
- * Generates a cryptoraphically random nonce of NONCE_LENGTH_IN_BYTES bytes.
- */
- private static byte[] generateRandomNonce() {
- SecureRandom rng = new SecureRandom();
- byte[] randomNonce = new byte[NONCE_LENGTH_IN_BYTES];
- rng.nextBytes(randomNonce);
- return randomNonce;
- }
-
- /**
- * Handy wrapper to do SHA512.
- */
- private byte[] sha512(byte[] input) throws HandshakeException {
- MessageDigest sha512;
- try {
- sha512 = MessageDigest.getInstance("SHA-512");
- return sha512.digest(input);
- } catch (NoSuchAlgorithmException e) {
- throwHandshakeException("No security provider initialized yet?", e);
- return null; // unreachable in practice, but makes compiler happy
- }
- }
-
- // Exception wrappers that remember to set the handshake state to ERROR
-
- private void throwAlertException(Ukey2Alert.AlertType alertType, String alertLogStatement)
- throws AlertException, HandshakeException {
- handshakeState = InternalState.HANDSHAKE_ERROR;
- throw new AlertException(alertLogStatement, makeAlertMessage(alertType, alertLogStatement));
- }
-
- private void throwHandshakeException(String logMessage) throws HandshakeException {
- handshakeState = InternalState.HANDSHAKE_ERROR;
- throw new HandshakeException(logMessage);
- }
-
- private void throwHandshakeException(Exception e) throws HandshakeException {
- handshakeState = InternalState.HANDSHAKE_ERROR;
- throw new HandshakeException(e);
- }
-
- private void throwHandshakeException(String logMessage, Exception e) throws HandshakeException {
- handshakeState = InternalState.HANDSHAKE_ERROR;
- throw new HandshakeException(logMessage, e);
- }
-
- private void throwIllegalStateException(String logMessage) {
- handshakeState = InternalState.HANDSHAKE_ERROR;
- throw new IllegalStateException(logMessage);
- }
-
- private void throwIllegalArgumentException(String logMessage) {
- handshakeState = InternalState.HANDSHAKE_ERROR;
- throw new IllegalArgumentException(logMessage);
- }
-}
diff --git a/src/main/java/com/google/security/cryptauth/lib/securegcm/device_to_device_messages_config.asciipb b/src/main/java/com/google/security/cryptauth/lib/securegcm/device_to_device_messages_config.asciipb
deleted file mode 100644
index 0e2952c..0000000
--- a/src/main/java/com/google/security/cryptauth/lib/securegcm/device_to_device_messages_config.asciipb
+++ /dev/null
@@ -1,3 +0,0 @@
-optimize_mode: LITE_RUNTIME
-
-allowed_message: "securegcm.DeviceToDeviceMessage"
diff --git a/src/main/java/com/google/security/cryptauth/lib/securegcm/securegcm_config.asciipb b/src/main/java/com/google/security/cryptauth/lib/securegcm/securegcm_config.asciipb
deleted file mode 100644
index d838bd3..0000000
--- a/src/main/java/com/google/security/cryptauth/lib/securegcm/securegcm_config.asciipb
+++ /dev/null
@@ -1,4 +0,0 @@
-optimize_mode: LITE_RUNTIME
-
-allowed_enum: "securegcm.Type"
-allowed_message: "securegcm.GcmMetadata"
diff --git a/src/main/java/com/google/security/cryptauth/lib/securemessage/CryptoOps.java b/src/main/java/com/google/security/cryptauth/lib/securemessage/CryptoOps.java
deleted file mode 100644
index 876bd93..0000000
--- a/src/main/java/com/google/security/cryptauth/lib/securemessage/CryptoOps.java
+++ /dev/null
@@ -1,564 +0,0 @@
-// Copyright 2020 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
-//
-// https://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.security.cryptauth.lib.securemessage;
-
-import com.google.security.annotations.SuppressInsecureCipherModeCheckerReviewed;
-import java.io.UnsupportedEncodingException;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.Key;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.SecureRandom;
-import java.security.Signature;
-import java.security.SignatureException;
-import javax.annotation.Nullable;
-import javax.crypto.BadPaddingException;
-import javax.crypto.Cipher;
-import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.Mac;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.IvParameterSpec;
-import javax.crypto.spec.SecretKeySpec;
-
-/**
- * Encapsulates the cryptographic operations used by the {@code SecureMessage*} classes.
- */
-public class CryptoOps {
-
- private CryptoOps() {} // Do not instantiate
-
- /**
- * Enum of supported signature types, with additional mappings to indicate the name of the
- * underlying JCA algorithm used to create the signature.
- * @see <a href=
- * "http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html">
- * Java Cryptography Architecture, Standard Algorithm Name Documentation</a>
- */
- public enum SigType {
- HMAC_SHA256(SecureMessageProto.SigScheme.HMAC_SHA256, "HmacSHA256", false),
- ECDSA_P256_SHA256(SecureMessageProto.SigScheme.ECDSA_P256_SHA256, "SHA256withECDSA", true),
- RSA2048_SHA256(SecureMessageProto.SigScheme.RSA2048_SHA256, "SHA256withRSA", true);
-
- public SecureMessageProto.SigScheme getSigScheme() {
- return sigScheme;
- }
-
- public String getJcaName() {
- return jcaName;
- }
-
- public boolean isPublicKeyScheme() {
- return publicKeyScheme;
- }
-
- public static SigType valueOf(SecureMessageProto.SigScheme sigScheme) {
- for (SigType value : values()) {
- if (value.sigScheme.equals(sigScheme)) {
- return value;
- }
- }
- throw new IllegalArgumentException("Unsupported SigType: " + sigScheme);
- }
-
- private final SecureMessageProto.SigScheme sigScheme;
- private final String jcaName;
- private final boolean publicKeyScheme;
-
- SigType(SecureMessageProto.SigScheme sigType, String jcaName, boolean publicKeyScheme) {
- this.sigScheme = sigType;
- this.jcaName = jcaName;
- this.publicKeyScheme = publicKeyScheme;
- }
- }
-
- /**
- * Enum of supported encryption types, with additional mappings to indicate the name of the
- * underlying JCA algorithm used to perform the encryption.
- * @see <a href=
- * "http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html">
- * Java Cryptography Architecture, Standard Algorithm Name Documentation</a>
- */
- public enum EncType {
- NONE(SecureMessageProto.EncScheme.NONE, "InvalidDoNotUseForJCA"),
- AES_256_CBC(SecureMessageProto.EncScheme.AES_256_CBC, "AES/CBC/PKCS5Padding");
-
- public SecureMessageProto.EncScheme getEncScheme() {
- return encScheme;
- }
-
- public String getJcaName() {
- return jcaName;
- }
-
- public static EncType valueOf(SecureMessageProto.EncScheme encScheme) {
- for (EncType value : values()) {
- if (value.encScheme.equals(encScheme)) {
- return value;
- }
- }
- throw new IllegalArgumentException("Unsupported EncType: " + encScheme);
- }
-
- private final SecureMessageProto.EncScheme encScheme;
- private final String jcaName;
-
- EncType(SecureMessageProto.EncScheme encScheme, String jcaName) {
- this.encScheme = encScheme;
- this.jcaName = jcaName;
- }
- }
-
- /**
- * Truncated hash output length, in bytes.
- */
- static final int DIGEST_LENGTH = 20;
- /**
- * A salt value specific to this library, generated as SHA-256("SecureMessage")
- */
- private static final byte[] SALT = sha256("SecureMessage");
-
- /**
- * Signs {@code data} using the algorithm specified by {@code sigType} with {@code signingKey}.
- *
- * @param rng is required for public key signature schemes
- * @return raw signature
- * @throws InvalidKeyException if {@code signingKey} is incompatible with {@code sigType}
- * @throws NoSuchAlgorithmException if the security provider is inadequate for {@code sigType}
- */
- static byte[] sign(
- SigType sigType, Key signingKey, @Nullable SecureRandom rng, byte[] data)
- throws InvalidKeyException, NoSuchAlgorithmException {
- if ((signingKey == null) || (data == null)) {
- throw new NullPointerException();
- }
- if (sigType.isPublicKeyScheme()) {
- if (rng == null) {
- throw new NullPointerException();
- }
- if (!(signingKey instanceof PrivateKey)) {
- throw new InvalidKeyException("Expected a PrivateKey");
- }
- Signature sigScheme = Signature.getInstance(sigType.getJcaName());
- sigScheme.initSign((PrivateKey) signingKey, rng);
- try {
- // We include a fixed magic value (salt) in the signature so that if the signing key is
- // reused in another context we can't be confused -- provided that the other user of the
- // signing key only signs statements that do not begin with this salt.
- sigScheme.update(SALT);
- sigScheme.update(data);
- return sigScheme.sign();
- } catch (SignatureException e) {
- throw new IllegalStateException(e); // Consistent with failures in Mac.doFinal
- }
- } else {
- Mac macScheme = Mac.getInstance(sigType.getJcaName());
- // Note that an AES-256 SecretKey should work with most Mac schemes
- SecretKey derivedKey = deriveAes256KeyFor(getSecretKey(signingKey), getPurpose(sigType));
- macScheme.init(derivedKey);
- return macScheme.doFinal(data);
- }
- }
-
- /**
- * Verifies the {@code signature} on {@code data} using the algorithm specified by
- * {@code sigType} with {@code verificationKey}.
- *
- * @return true iff the signature is verified
- * @throws NoSuchAlgorithmException if the security provider is inadequate for {@code sigType}
- * @throws InvalidKeyException if {@code verificationKey} is incompatible with {@code sigType}
- * @throws SignatureException
- */
- static boolean verify(Key verificationKey, SigType sigType, byte[] signature, byte[] data)
- throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
- if ((verificationKey == null) || (signature == null) || (data == null)) {
- throw new NullPointerException();
- }
- if (sigType.isPublicKeyScheme()) {
- if (!(verificationKey instanceof PublicKey)) {
- throw new InvalidKeyException("Expected a PublicKey");
- }
- Signature sigScheme = Signature.getInstance(sigType.getJcaName());
- sigScheme.initVerify((PublicKey) verificationKey);
- sigScheme.update(SALT); // See the comments in sign() for more on this
- sigScheme.update(data);
- return sigScheme.verify(signature);
- } else {
- Mac macScheme = Mac.getInstance(sigType.getJcaName());
- SecretKey derivedKey =
- deriveAes256KeyFor(getSecretKey(verificationKey), getPurpose(sigType));
- macScheme.init(derivedKey);
- return constantTimeArrayEquals(signature, macScheme.doFinal(data));
- }
- }
-
- /**
- * Generate a random IV appropriate for use with the algorithm specified in {@code encType}.
- *
- * @return a freshly generated IV (a random byte sequence of appropriate length)
- * @throws NoSuchAlgorithmException if the security provider is inadequate for {@code encType}
- */
- @SuppressInsecureCipherModeCheckerReviewed
- // See b/26525455 for security review.
- static byte[] generateIv(EncType encType, SecureRandom rng) throws NoSuchAlgorithmException {
- if (rng == null) {
- throw new NullPointerException();
- }
- try {
- Cipher encrypter = Cipher.getInstance(encType.getJcaName());
- byte[] iv = new byte[encrypter.getBlockSize()];
- rng.nextBytes(iv);
- return iv;
- } catch (NoSuchPaddingException e) {
- throw new NoSuchAlgorithmException(e); // Consolidate into NoSuchAlgorithmException
- }
- }
-
- /**
- * Encrypts {@code plaintext} using the algorithm specified in {@code encType}, with the specified
- * {@code iv} and {@code encryptionKey}.
- *
- * @param rng source of randomness to be used with the specified cipher, if necessary
- * @return encrypted data
- * @throws NoSuchAlgorithmException if the security provider is inadequate for {@code encType}
- * @throws InvalidKeyException if {@code encryptionKey} is incompatible with {@code encType}
- */
- @SuppressInsecureCipherModeCheckerReviewed
- // See b/26525455 for security review.
- static byte[] encrypt(
- Key encryptionKey, EncType encType, @Nullable SecureRandom rng, byte[] iv, byte[] plaintext)
- throws NoSuchAlgorithmException, InvalidKeyException {
- if ((encryptionKey == null) || (iv == null) || (plaintext == null)) {
- throw new NullPointerException();
- }
- if (encType == EncType.NONE) {
- throw new NoSuchAlgorithmException("Cannot use NONE type here");
- }
- try {
- Cipher encrypter = Cipher.getInstance(encType.getJcaName());
- SecretKey derivedKey =
- deriveAes256KeyFor(getSecretKey(encryptionKey), getPurpose(encType));
- encrypter.init(Cipher.ENCRYPT_MODE, derivedKey, new IvParameterSpec(iv), rng);
- return encrypter.doFinal(plaintext);
- } catch (InvalidAlgorithmParameterException e) {
- throw new AssertionError(e); // Should never happen
- } catch (IllegalBlockSizeException e) {
- throw new AssertionError(e); // Should never happen
- } catch (BadPaddingException e) {
- throw new AssertionError(e); // Should never happen
- } catch (NoSuchPaddingException e) {
- throw new NoSuchAlgorithmException(e); // Consolidate into NoSuchAlgorithmException
- }
- }
-
- /**
- * Decrypts {@code ciphertext} using the algorithm specified in {@code encType}, with the
- * specified {@code iv} and {@code decryptionKey}.
- *
- * @return the plaintext (decrypted) data
- * @throws NoSuchAlgorithmException if the security provider is inadequate for {@code encType}
- * @throws InvalidKeyException if {@code decryptionKey} is incompatible with {@code encType}
- * @throws InvalidAlgorithmParameterException if {@code encType} exceeds legal cryptographic
- * strength limits in this jurisdiction
- * @throws IllegalBlockSizeException if {@code ciphertext} contains an illegal block
- * @throws BadPaddingException if {@code ciphertext} contains an illegal padding
- */
- @SuppressInsecureCipherModeCheckerReviewed
- // See b/26525455 for security review
- static byte[] decrypt(Key decryptionKey, EncType encType, byte[] iv, byte[] ciphertext)
- throws NoSuchAlgorithmException, InvalidKeyException, InvalidAlgorithmParameterException,
- IllegalBlockSizeException, BadPaddingException {
- if ((decryptionKey == null) || (iv == null) || (ciphertext == null)) {
- throw new NullPointerException();
- }
- if (encType == EncType.NONE) {
- throw new NoSuchAlgorithmException("Cannot use NONE type here");
- }
- try {
- Cipher decrypter = Cipher.getInstance(encType.getJcaName());
- SecretKey derivedKey =
- deriveAes256KeyFor(getSecretKey(decryptionKey), getPurpose(encType));
- decrypter.init(Cipher.DECRYPT_MODE, derivedKey, new IvParameterSpec(iv));
- return decrypter.doFinal(ciphertext);
- } catch (NoSuchPaddingException e) {
- throw new AssertionError(e); // Should never happen
- }
- }
-
- /**
- * Computes a collision-resistant hash of {@link #DIGEST_LENGTH} bytes
- * (using a truncated SHA-256 output).
- */
- static byte[] digest(byte[] data) throws NoSuchAlgorithmException {
- MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
- byte[] truncatedHash = new byte[DIGEST_LENGTH];
- System.arraycopy(sha256.digest(data), 0, truncatedHash, 0, DIGEST_LENGTH);
- return truncatedHash;
- }
-
- /**
- * Returns {@code true} if the two arrays are equal to one another.
- * When the two arrays differ in length, trivially returns {@code false}.
- * When the two arrays are equal in length, does a constant-time comparison
- * of the two, i.e. does not abort the comparison when the first differing
- * element is found.
- *
- * <p>NOTE: This is a copy of {@code java/com/google/math/crypto/ConstantTime#arrayEquals}.
- *
- * @param a An array to compare
- * @param b Another array to compare
- * @return {@code true} if these arrays are both null or if they have equal
- * length and equal bytes in all elements
- */
- static boolean constantTimeArrayEquals(@Nullable byte[] a, @Nullable byte[] b) {
- if (a == null || b == null) {
- return (a == b);
- }
- if (a.length != b.length) {
- return false;
- }
- byte result = 0;
- for (int i = 0; i < b.length; i++) {
- result = (byte) (result | a[i] ^ b[i]);
- }
- return (result == 0);
- }
-
- // @VisibleForTesting
- static String getPurpose(SigType sigType) {
- return "SIG:" + sigType.getSigScheme().getNumber();
- }
-
- // @VisibleForTesting
- static String getPurpose(EncType encType) {
- return "ENC:" + encType.getEncScheme().getNumber();
- }
-
- private static SecretKey getSecretKey(Key key) throws InvalidKeyException {
- if (!(key instanceof SecretKey)) {
- throw new InvalidKeyException("Expected a SecretKey");
- }
- return (SecretKey) key;
- }
-
- /**
- * @return the UTF-8 encoding of the given string
- * @throws RuntimeException if the UTF-8 charset is not present.
- */
- public static byte[] utf8StringToBytes(String input) {
- try {
- return input.getBytes("UTF-8");
- } catch (UnsupportedEncodingException e) {
- throw new RuntimeException(e); // Shouldn't happen, UTF-8 is universal
- }
- }
-
- /**
- * @return SHA-256(UTF-8 encoded input)
- */
- public static byte[] sha256(String input) {
- MessageDigest sha256;
- try {
- sha256 = MessageDigest.getInstance("SHA-256");
- return sha256.digest(utf8StringToBytes(input));
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException("No security provider initialized yet?", e);
- }
- }
-
- /**
- * A key derivation function specific to this library, which accepts a {@code masterKey} and an
- * arbitrary {@code purpose} describing the intended application of the derived sub-key,
- * and produces a derived AES-256 key safe to use as if it were independent of any other
- * derived key which used a different {@code purpose}.
- *
- * @param masterKey any key suitable for use with HmacSHA256
- * @param purpose a UTF-8 encoded string describing the intended purpose of derived key
- * @return a derived SecretKey suitable for use with AES-256
- * @throws InvalidKeyException if the encoded form of {@code masterKey} cannot be accessed
- */
- static SecretKey deriveAes256KeyFor(SecretKey masterKey, String purpose)
- throws NoSuchAlgorithmException, InvalidKeyException {
- return new SecretKeySpec(hkdf(masterKey, SALT, utf8StringToBytes(purpose)), "AES");
- }
-
- /**
- * Implements HKDF (RFC 5869) with the SHA-256 hash and a 256-bit output key length.
- *
- * Please make sure to select a salt that is fixed and unique for your codebase, and use the
- * {@code info} parameter to specify any additional bits that should influence the derived key.
- *
- * @param inputKeyMaterial master key from which to derive sub-keys
- * @param salt a (public) randomly generated 256-bit input that can be re-used
- * @param info arbitrary information that is bound to the derived key (i.e., used in its creation)
- * @return raw derived key bytes = HKDF-SHA256(inputKeyMaterial, salt, info)
- * @throws InvalidKeyException if the encoded form of {@code inputKeyMaterial} cannot be accessed
- */
- public static byte[] hkdf(SecretKey inputKeyMaterial, byte[] salt, byte[] info)
- throws NoSuchAlgorithmException, InvalidKeyException {
- return hkdf(inputKeyMaterial, salt, info, /* length= */ 32);
- }
-
- /**
- * Implements HKDF (RFC 5869) with the SHA-256 hash.
- *
- * <p>Please make sure to select a salt that is fixed and unique for your codebase, and use the
- * {@code info} parameter to specify any additional bits that should influence the derived key.
- *
- * @param inputKeyMaterial master key from which to derive sub-keys
- * @param salt a (public) randomly generated 256-bit input that can be re-used
- * @param info arbitrary information that is bound to the derived key (i.e., used in its creation)
- * @param length length of returned key material
- * @return raw derived key bytes = HKDF-SHA256(inputKeyMaterial, salt, info)
- * @throws InvalidKeyException if the encoded form of {@code inputKeyMaterial} cannot be accessed
- */
- public static byte[] hkdf(SecretKey inputKeyMaterial, byte[] salt, byte[] info, int length)
- throws NoSuchAlgorithmException, InvalidKeyException {
- if ((inputKeyMaterial == null) || (salt == null) || (info == null)) {
- throw new NullPointerException();
- }
- if (length < 0) {
- throw new IllegalArgumentException("Length must be positive");
- }
- return hkdfSha256Expand(hkdfSha256Extract(inputKeyMaterial, salt), info, length);
- }
-
- /**
- * @return the concatenation of {@code a} and {@code b}, treating {@code null} as the empty array.
- */
- static byte[] concat(@Nullable byte[] a, @Nullable byte[] b) {
- if ((a == null) && (b == null)) {
- return new byte[] { };
- }
- if (a == null) {
- return b;
- }
- if (b == null) {
- return a;
- }
- byte[] result = new byte[a.length + b.length];
- System.arraycopy(a, 0, result, 0, a.length);
- System.arraycopy(b, 0, result, a.length, b.length);
- return result;
- }
-
- /**
- * Since {@code Arrays.copyOfRange(...)} is not available on older Android platforms,
- * a custom method for computing a subarray is provided here.
- *
- * @return the substring of {@code in} from {@code beginIndex} (inclusive)
- * up to {@code endIndex} (exclusive)
- */
- static byte[] subarray(byte[] in, int beginIndex, int endIndex) {
- if (in == null) {
- throw new NullPointerException();
- }
- int length = endIndex - beginIndex;
- if ((length < 0)
- || (beginIndex < 0)
- || (endIndex < 0)
- || (beginIndex >= in.length)
- || (endIndex > in.length)) {
- throw new IndexOutOfBoundsException();
- }
- byte[] result = new byte[length];
- if (length > 0) {
- System.arraycopy(in, beginIndex, result, 0, length);
- }
- return result;
- }
-
- /**
- * The HKDF (RFC 5869) extraction function, using the SHA-256 hash function. This function is used
- * to pre-process the inputKeyMaterial and mix it with the salt, producing output suitable for use
- * with HKDF expansion function (which produces the actual derived key).
- *
- * @see #hkdfSha256Expand(byte[], byte[], int)
- * @return HMAC-SHA256(salt, inputKeyMaterial) (salt is the "key" for the HMAC)
- * @throws InvalidKeyException if the encoded form of {@code inputKeyMaterial} cannot be accessed
- * @throws NoSuchAlgorithmException if the HmacSHA256 or AES algorithms are unavailable
- */
- private static byte[] hkdfSha256Extract(SecretKey inputKeyMaterial, byte[] salt)
- throws NoSuchAlgorithmException, InvalidKeyException {
- Mac macScheme = Mac.getInstance("HmacSHA256");
- try {
- macScheme.init(new SecretKeySpec(salt, "AES"));
- } catch (InvalidKeyException e) {
- throw new AssertionError(e); // This should never happen
- }
- // Note that the SecretKey encoding format is defined to be RAW, so the encoded form should be
- // consistent across implementations.
- byte[] encodedKeyMaterial = inputKeyMaterial.getEncoded();
- if (encodedKeyMaterial == null) {
- throw new InvalidKeyException("Cannot get encoded form of SecretKey");
- }
- return macScheme.doFinal(encodedKeyMaterial);
- }
-
- /**
- * HKDF (RFC 5869) expansion function, using the SHA-256 hash function.
- *
- * @param pseudoRandomKey should be generated by {@link #hkdfSha256Extract(SecretKey, byte[])}
- * @param info arbitrary information the derived key should be bound to
- * @param length length of the output key material in bytes
- * @return raw derived key bytes = HMAC-SHA256(pseudoRandomKey, info | 0x01)
- * @throws NoSuchAlgorithmException if the HmacSHA256 or AES algorithms are unavailable
- */
- private static byte[] hkdfSha256Expand(byte[] pseudoRandomKey, byte[] info, int length)
- throws NoSuchAlgorithmException {
- Mac macScheme = Mac.getInstance("HmacSHA256");
- try {
- macScheme.init(new SecretKeySpec(pseudoRandomKey, "AES"));
- } catch (InvalidKeyException e) {
- throw new AssertionError(e); // This should never happen
- }
-
- // Number of blocks N = ceil(hash length / output length).
- int blocks = length / 32;
- if (length % 32 > 0) {
- blocks += 1;
- }
-
- // The counter used to generate the blocks according to the RFC is only one byte long,
- // which puts a limit on the number of blocks possible.
- if (blocks > 0xFF) {
- throw new IllegalArgumentException("Maximum HKDF output length exceeded.");
- }
-
- byte[] outputBlock = new byte[32];
- byte[] counter = new byte[1];
- byte[] output = new byte[32 * blocks];
- for (int i = 0; i < blocks; ++i) {
- macScheme.reset();
- if (i > 0) {
- // Previous block
- macScheme.update(outputBlock);
- }
- // Arbitrary info
- macScheme.update(info);
- // Counter
- counter[0] = (byte) (i + 1);
- outputBlock = macScheme.doFinal(counter);
-
- System.arraycopy(outputBlock, 0, output, 32 * i, 32);
- }
-
- return subarray(output, 0, length);
- }
-
-}
diff --git a/src/main/java/com/google/security/cryptauth/lib/securemessage/PublicKeyProtoUtil.java b/src/main/java/com/google/security/cryptauth/lib/securemessage/PublicKeyProtoUtil.java
deleted file mode 100644
index 0c593fe..0000000
--- a/src/main/java/com/google/security/cryptauth/lib/securemessage/PublicKeyProtoUtil.java
+++ /dev/null
@@ -1,675 +0,0 @@
-// Copyright 2020 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
-//
-// https://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.security.cryptauth.lib.securemessage;
-
-import com.google.common.collect.Lists;
-import com.google.protobuf.ByteString;
-import com.google.security.annotations.SuppressInsecureCipherModeCheckerPendingReview;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.DhPublicKey;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.EcP256PublicKey;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.GenericPublicKey;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.SimpleRsaPublicKey;
-import java.math.BigInteger;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.KeyFactory;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.NoSuchAlgorithmException;
-import java.security.PublicKey;
-import java.security.SecureRandom;
-import java.security.interfaces.ECPublicKey;
-import java.security.interfaces.RSAPublicKey;
-import java.security.spec.ECFieldFp;
-import java.security.spec.ECGenParameterSpec;
-import java.security.spec.ECParameterSpec;
-import java.security.spec.ECPoint;
-import java.security.spec.ECPublicKeySpec;
-import java.security.spec.InvalidKeySpecException;
-import java.security.spec.RSAPublicKeySpec;
-import javax.crypto.interfaces.DHPrivateKey;
-import javax.crypto.interfaces.DHPublicKey;
-import javax.crypto.spec.DHParameterSpec;
-import javax.crypto.spec.DHPublicKeySpec;
-
-/**
- * Utility class containing static factory methods for a simple protobuf based representation of
- * EC public keys that is intended for use with the SecureMessage library.
- *
- * N.B.: Requires the availability of an EC security provider supporting the NIST P-256 curve.
- *
- */
-public class PublicKeyProtoUtil {
-
- private PublicKeyProtoUtil() {} // Do not instantiate
-
- /**
- * Caches state about whether the current platform supports Elliptic Curve algorithms.
- */
- private static final Boolean IS_LEGACY_CRYPTO_REQUIRED = determineIfLegacyCryptoRequired();
-
- private static final BigInteger ONE = new BigInteger("1");
- private static final BigInteger TWO = new BigInteger("2");
-
- /**
- * Name for Elliptic Curve cryptography algorithm suite, used by the security provider. If the
- * security provider does not implement the specified algorithm, runtime errors will ensue.
- */
- private static final String EC_ALG = "EC";
-
- /**
- * A common name for the NIST P-256 curve, used by most Java security providers.
- */
- private static final String EC_P256_COMMON_NAME = "secp256r1";
-
- /**
- * A name the NIST P-256 curve, used by the OpenSSL Java security provider (e.g,. on Android).
- */
- private static final String EC_P256_OPENSSL_NAME = "prime256v1";
-
- /**
- * The {@link ECParameterSpec} for the NIST P-256 Elliptic Curve.
- */
- private static final ECParameterSpec EC_P256_PARAMS = isLegacyCryptoRequired() ? null :
- ((ECPublicKey) generateEcP256KeyPair().getPublic()).getParams();
-
- /**
- * The prime {@code p} describing the field for the NIST P-256 curve.
- */
- private static final BigInteger EC_P256_P = isLegacyCryptoRequired() ? null :
- ((ECFieldFp) EC_P256_PARAMS.getCurve().getField()).getP();
-
- /**
- * The coefficient {@code a} for the NIST P-256 curve.
- */
- private static final BigInteger EC_P256_A = isLegacyCryptoRequired() ? null :
- EC_P256_PARAMS.getCurve().getA();
-
- /**
- * The coefficient {@code b} for the NIST P-256 curve.
- */
- private static final BigInteger EC_P256_B = isLegacyCryptoRequired() ? null :
- EC_P256_PARAMS.getCurve().getB();
-
- /**
- * Maximum number of bytes in a 2's complement encoding of a NIST P-256 elliptic curve point.
- */
- private static final int MAX_P256_ENCODING_BYTES = 33;
-
- /**
- * The JCA name for the RSA cryptography suite.
- */
- private static final String RSA_ALG = "RSA";
-
- private static final int RSA2048_MODULUS_BITS = 2048;
-
- /**
- * Maximum number of bytes in a 2's complement encoding of a 2048-bit RSA key.
- */
- private static final int MAX_RSA2048_ENCODING_BYTES = 257;
-
- /**
- * The JCA name for the Diffie-Hellman cryptography suite.
- */
- private static final String DH_ALG = "DH";
-
- /**
- * The prime from the 2048-bit MODP Group (group 14) described in RFC 3526, to be used for
- * Diffie-Hellman computations. Use only if Elliptic Curve ciphers are unavailable.
- */
- public static final BigInteger DH_P = new BigInteger(
- "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" +
- "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" +
- "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" +
- "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" +
- "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" +
- "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" +
- "83655D23DCA3AD961C62F356208552BB9ED529077096966D" +
- "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" +
- "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" +
- "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" +
- "15728E5A8AACAA68FFFFFFFFFFFFFFFF", 16);
-
- /**
- * The generator for the 2048-bit MODP Group (group 14) described in RFC 3526, to be used for
- * Diffie-Hellman computations. Use only if Elliptic Curve ciphers are unavailable.
- */
- public static final BigInteger DH_G = TWO;
-
- /**
- * The size of the Diffie-Hellman exponent to use, in bits.
- */
- public static final int DH_LEN = 512;
-
- /**
- * Maximum number of bytes in a 2's complement encoding of a
- * Diffie-Hellman key using {@link #DH_G}.
- */
- private static final int MAX_DH2048_ENCODING_BYTES = 257;
-
- /**
- * Version code for the Honeycomb release of Android, which is the first release supporting
- * Elliptic Curve.
- */
- public static final int ANDROID_HONEYCOMB_SDK_INT = 11;
-
- /**
- * Encodes any supported {@link PublicKey} type as a {@link GenericPublicKey} proto message.
- *
- * @see SecureMessageProto constants (defined in the .proto file) for supported types
- */
- public static GenericPublicKey encodePublicKey(PublicKey pk) {
- if (pk == null) {
- throw new NullPointerException();
- }
- if (pk instanceof ECPublicKey) {
- return GenericPublicKey.newBuilder()
- .setType(SecureMessageProto.PublicKeyType.EC_P256)
- .setEcP256PublicKey(encodeEcPublicKey(pk))
- .build();
- }
- if (pk instanceof RSAPublicKey) {
- return GenericPublicKey.newBuilder()
- .setType(SecureMessageProto.PublicKeyType.RSA2048)
- .setRsa2048PublicKey(encodeRsa2048PublicKey(pk))
- .build();
- }
- if (pk instanceof DHPublicKey) {
- return GenericPublicKey.newBuilder()
- .setType(SecureMessageProto.PublicKeyType.DH2048_MODP)
- .setDh2048PublicKey(encodeDh2048PublicKey(pk))
- .build();
- }
- throw new IllegalArgumentException("Unsupported PublicKey type");
- }
-
- /**
- * Encodes an {@link ECPublicKey} to an {@link GenericPublicKey} proto message. The returned key
- * has a null-byte padded to the front in order to match the C++ implementation.
- */
- public static GenericPublicKey encodePaddedEcPublicKey(PublicKey pk) {
- if (pk == null) {
- throw new NullPointerException();
- }
- if (!(pk instanceof ECPublicKey)) {
- throw new IllegalArgumentException("Expected ECPublicKey PublicKey type");
- }
-
- ECPublicKey epk = pkToECPublicKey(pk);
- ByteString nullByteString = ByteString.copyFrom(new byte[] {0});
- ByteString xByteString = extractX(epk);
- if (xByteString.size() < MAX_P256_ENCODING_BYTES) {
- xByteString = ByteString.copyFrom(Lists.newArrayList(nullByteString, xByteString));
- }
- ByteString yByteString = extractY(epk);
- if (yByteString.size() < MAX_P256_ENCODING_BYTES) {
- yByteString = ByteString.copyFrom(Lists.newArrayList(nullByteString, yByteString));
- }
- EcP256PublicKey newKey =
- EcP256PublicKey.newBuilder().setX(xByteString).setY(yByteString).build();
-
- return GenericPublicKey.newBuilder()
- .setType(SecureMessageProto.PublicKeyType.EC_P256)
- .setEcP256PublicKey(newKey)
- .build();
- }
-
- /**
- * Encodes an {@link ECPublicKey} to an {@link EcP256PublicKey} proto message.
- */
- public static EcP256PublicKey encodeEcPublicKey(PublicKey pk) {
- ECPublicKey epk = pkToECPublicKey(pk);
- return EcP256PublicKey.newBuilder()
- .setX(extractX(epk))
- .setY(extractY(epk))
- .build();
- }
-
- /**
- * Encodes a 2048-bit {@link RSAPublicKey} to an {@link SimpleRsaPublicKey} proto message.
- */
- public static SimpleRsaPublicKey encodeRsa2048PublicKey(PublicKey pk) {
- RSAPublicKey rpk = pkToRSAPublicKey(pk);
- return SimpleRsaPublicKey.newBuilder()
- .setN(ByteString.copyFrom(rpk.getModulus().toByteArray()))
- .setE(rpk.getPublicExponent().intValue())
- .build();
- }
-
- /**
- * Encodes a 2048-bit {@link DhPublicKey} using the {@link #DH_G} group to a
- * {@link DhPublicKey} proto message.
- */
- public static DhPublicKey encodeDh2048PublicKey(PublicKey pk) {
- DHPublicKey dhpk = pkToDHPublicKey(pk);
- return DhPublicKey.newBuilder()
- .setY(ByteString.copyFrom(dhpk.getY().toByteArray()))
- .build();
- }
-
- /**
- * Extracts a {@link PublicKey} from an {@link GenericPublicKey} proto message.
- *
- * @throws InvalidKeySpecException if the input is not a valid and/or supported public key type
- */
- public static PublicKey parsePublicKey(GenericPublicKey gpk) throws InvalidKeySpecException {
- if (!gpk.hasType()) {
- // "required" means nothing in micro proto land. We have to check this ourselves.
- throw new InvalidKeySpecException("GenericPublicKey.type is a required field");
- }
- switch (gpk.getType()) {
- case EC_P256:
- if (!gpk.hasEcP256PublicKey()) {
- break;
- }
- return parseEcPublicKey(gpk.getEcP256PublicKey());
- case RSA2048:
- if (!gpk.hasRsa2048PublicKey()) {
- break;
- }
- return parseRsa2048PublicKey(gpk.getRsa2048PublicKey());
- case DH2048_MODP:
- if (!gpk.hasDh2048PublicKey()) {
- break;
- }
- return parseDh2048PublicKey(gpk.getDh2048PublicKey());
- default:
- throw new InvalidKeySpecException("Unsupported GenericPublicKey type: " + gpk.getType());
- }
- throw new InvalidKeySpecException("key object is missing for key type: " + gpk.getType());
- }
-
- /**
- * Extracts a {@link ECPublicKey} from an {@link EcP256PublicKey} proto message.
- *
- * @throws InvalidKeySpecException if the input is not a valid NIST P-256 public key or if
- * this platform does not support Elliptic Curve keys
- */
- public static ECPublicKey parseEcPublicKey(EcP256PublicKey p256pk)
- throws InvalidKeySpecException {
- if (!p256pk.hasX() || !p256pk.hasY()) {
- throw new InvalidKeySpecException("Key is missing a required coordinate");
- }
- if (isLegacyCryptoRequired()) {
- throw new InvalidKeySpecException("Elliptic Curve keys not supported on this platform");
- }
- byte[] encodedX = p256pk.getX().toByteArray();
- byte[] encodedY = p256pk.getY().toByteArray();
- try {
- validateEcP256CoordinateEncoding(encodedX);
- validateEcP256CoordinateEncoding(encodedY);
- BigInteger wX = new BigInteger(encodedX);
- BigInteger wY = new BigInteger(encodedY);
- validateEcP256CurvePoint(wX, wY);
- return (ECPublicKey) KeyFactory.getInstance(EC_ALG).generatePublic(
- new ECPublicKeySpec(new ECPoint(wX, wY), EC_P256_PARAMS));
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Extracts a {@link RSAPublicKey} from an {@link SimpleRsaPublicKey} proto message.
- *
- * @throws InvalidKeySpecException when the input RSA public key is invalid
- */
- public static RSAPublicKey parseRsa2048PublicKey(SimpleRsaPublicKey pk)
- throws InvalidKeySpecException {
- if (!pk.hasN()) {
- throw new InvalidKeySpecException("required field is missing");
- }
- byte[] encodedN = pk.getN().toByteArray();
- validateSimpleRsaEncoding(encodedN);
- BigInteger n = new BigInteger(encodedN);
- if (n.bitLength() != RSA2048_MODULUS_BITS) {
- throw new InvalidKeySpecException();
- }
- BigInteger e = BigInteger.valueOf(pk.getE());
- try {
- return (RSAPublicKey) KeyFactory.getInstance(RSA_ALG).generatePublic(
- new RSAPublicKeySpec(n, e));
- } catch (NoSuchAlgorithmException e1) {
- throw new AssertionError(e1); // Should never happen
- }
- }
-
- /**
- * Extracts a {@link DHPublicKey} from an {@link DhPublicKey} proto message.
- *
- * @throws InvalidKeySpecException when the input DH public key is invalid
- */
- @SuppressInsecureCipherModeCheckerPendingReview // b/32143855
- public static DHPublicKey parseDh2048PublicKey(DhPublicKey pk) throws InvalidKeySpecException {
- if (!pk.hasY()) {
- throw new InvalidKeySpecException("required field is missing");
- }
- byte[] encodedY = pk.getY().toByteArray();
- validateDhEncoding(encodedY);
- BigInteger y;
- try {
- y = new BigInteger(encodedY);
- } catch (NumberFormatException e) {
- throw new InvalidKeySpecException();
- }
- validateDhGroupElement(y);
- try {
- return (DHPublicKey) KeyFactory.getInstance(DH_ALG).generatePublic(
- new DHPublicKeySpec(y, DH_P, DH_G));
- } catch (NoSuchAlgorithmException e) {
- throw new AssertionError(e); // Should never happen
- }
- }
-
- /**
- * @return a freshly generated NIST P-256 Elliptic Curve key pair.
- */
- public static KeyPair generateEcP256KeyPair() {
- return getEcKeyGen().generateKeyPair();
- }
-
- /**
- * @return a freshly generated 2048-bit RSA key pair.
- */
- public static KeyPair generateRSA2048KeyPair() {
- return getRsaKeyGen().generateKeyPair();
- }
-
- /**
- * @return a freshly generated Diffie-Hellman key pair for the 2048-bit group
- * described by {@link #DH_G}
- */
- public static KeyPair generateDh2048KeyPair() {
- try {
- return getDhKeyGen().generateKeyPair();
- } catch (InvalidAlgorithmParameterException e) {
- // Construct an appropriate KeyPair manually, since this platform refuses to do it for us
- DHParameterSpec spec = new DHParameterSpec(DH_P, DH_G);
- BigInteger x = new BigInteger(DH_LEN, new SecureRandom());
- DHPrivateKey privateKey = new DHPrivateKeyShim(x, spec);
- DHPublicKey publicKey = new DHPublicKeyShim(DH_G.modPow(x, DH_P), spec);
- return new KeyPair(publicKey, privateKey);
- }
- }
-
- /**
- * A lightweight encoding for a {@link DHPrivateKey}. Strongly recommended over attempting to use
- * {@link DHPrivateKey#getEncoded()}, but not compatible with the standard encoding.
- *
- * @see #parseDh2048PrivateKey(byte[])
- */
- public static byte[] encodeDh2048PrivateKey(DHPrivateKey sk) {
- return sk.getX().toByteArray();
- }
-
- /**
- * Parses a {@link DHPrivateKey} encoded with {@link #encodeDh2048PrivateKey(DHPrivateKey)}.
- */
- public static DHPrivateKey parseDh2048PrivateKey(byte[] encodedX)
- throws InvalidKeySpecException {
- validateDhEncoding(encodedX); // Could be stricter for x, but should be fine to use this
- BigInteger x;
- try {
- x = new BigInteger(encodedX);
- } catch (NumberFormatException e) {
- throw new InvalidKeySpecException();
- }
- validateDhGroupElement(x); // Again, this validation should be good enough
- return new DHPrivateKeyShim(x, new DHParameterSpec(DH_P, DH_G));
- }
-
- /**
- * @throws InvalidKeySpecException if point ({@code x},{@code y}) isn't on the NIST P-256 curve
- */
- private static void validateEcP256CurvePoint(BigInteger x, BigInteger y)
- throws InvalidKeySpecException {
- if ((x.signum() == -1) || (y.signum() == -1)) {
- throw new InvalidKeySpecException("Point encoding must use only non-negative integers");
- }
-
- BigInteger p = EC_P256_P;
- if ((x.compareTo(p) >= 0) || (y.compareTo(p) >= 0)) {
- throw new InvalidKeySpecException("Point lies outside of the expected field");
- }
-
- // Points on the curve satisfy y^2 = x^3 + ax + b (mod p)
- BigInteger lhs = squareMod(y, p);
- BigInteger rhs = squareMod(x, p).add(EC_P256_A) // = (x^2 + a)
- .multiply(x).mod(p) // = x(x^2 + a) = x^3 + ax
- .add(EC_P256_B) // = x^3 + ax + b
- .mod(p);
- if (!lhs.equals(rhs)) {
- throw new InvalidKeySpecException("Point does not lie on the expected curve");
- }
- }
-
- /**
- * @return value of {@code x}^2 (mod {@code p})
- */
- private static BigInteger squareMod(BigInteger x, BigInteger p) {
- return x.multiply(x).mod(p);
- }
-
- /**
- * @throws InvalidKeySpecException if the coordinate is too large for a 256-bit curve
- */
- private static void validateEcP256CoordinateEncoding(byte[] p) throws InvalidKeySpecException {
- if ((p.length == 0)
- || (p.length > MAX_P256_ENCODING_BYTES)
- || (p.length == MAX_P256_ENCODING_BYTES && p[0] != 0)) {
- throw new InvalidKeySpecException(); // Intentionally vague for security reasons
- }
- }
-
- /**
- * @throws InvalidKeySpecException if the input is too large for a 2048-bit RSA modulus
- */
- private static void validateSimpleRsaEncoding(byte[] n) throws InvalidKeySpecException {
- if (n.length == 0 || n.length > MAX_RSA2048_ENCODING_BYTES) {
- throw new InvalidKeySpecException();
- }
- }
-
- /**
- * @throws InvalidKeySpecException if the public key is too large for a 2048-bit DH group
- */
- private static void validateDhEncoding(byte[] y) throws InvalidKeySpecException {
- if (y.length == 0 || y.length > MAX_DH2048_ENCODING_BYTES) {
- throw new InvalidKeySpecException();
- }
- }
-
- /**
- * @throws InvalidKeySpecException if {@code y} is not a valid Diffie-Hellman public key
- */
- private static void validateDhGroupElement(BigInteger y) throws InvalidKeySpecException {
- // Check that 1 < y < p -1
- if ((y.compareTo(ONE) < 1) || (y.compareTo(DH_P.subtract(ONE)) > -1)) {
- throw new InvalidKeySpecException();
- }
- }
-
- private static ByteString extractY(ECPublicKey epk) {
- return ByteString.copyFrom(epk.getW().getAffineY().toByteArray());
- }
-
- private static ByteString extractX(ECPublicKey epk) {
- return ByteString.copyFrom(epk.getW().getAffineX().toByteArray());
- }
-
- private static ECPublicKey pkToECPublicKey(PublicKey pk) {
- if (pk == null) {
- throw new NullPointerException();
- }
- if (!(pk instanceof ECPublicKey)) {
- throw new IllegalArgumentException("Not an EC Public Key");
- }
- return (ECPublicKey) pk;
- }
-
- private static RSAPublicKey pkToRSAPublicKey(PublicKey pk) {
- if (pk == null) {
- throw new NullPointerException();
- }
- if (!(pk instanceof RSAPublicKey)) {
- throw new IllegalArgumentException("Not an RSA Public Key");
- }
- return (RSAPublicKey) pk;
- }
-
- private static DHPublicKey pkToDHPublicKey(PublicKey pk) {
- if (pk == null) {
- throw new NullPointerException();
- }
- if (!(pk instanceof DHPublicKey)) {
- throw new IllegalArgumentException("Not a DH Public Key");
- }
- return (DHPublicKey) pk;
- }
-
- /**
- * @return an EC {@link KeyPairGenerator} object initialized for NIST P-256.
- */
- private static KeyPairGenerator getEcKeyGen() {
- KeyPairGenerator keygen;
- try {
- keygen = KeyPairGenerator.getInstance(EC_ALG);
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException(e);
- }
- try {
- // Try using the OpenSSL provider first, since we prefer it over BouncyCastle
- keygen.initialize(new ECGenParameterSpec(EC_P256_OPENSSL_NAME));
- return keygen;
- } catch (InvalidAlgorithmParameterException e) {
- // Try another name for NIST P-256
- }
- try {
- keygen.initialize(new ECGenParameterSpec(EC_P256_COMMON_NAME));
- return keygen;
- } catch (InvalidAlgorithmParameterException e) {
- throw new RuntimeException("Unable to find the NIST P-256 curve");
- }
- }
-
- /**
- * @return an RSA {@link KeyPairGenerator} object initialized for 2048-bit keys.
- */
- private static KeyPairGenerator getRsaKeyGen() {
- try {
- KeyPairGenerator keygen = KeyPairGenerator.getInstance(RSA_ALG);
- keygen.initialize(RSA2048_MODULUS_BITS);
- return keygen;
- } catch (NoSuchAlgorithmException e) {
- throw new AssertionError(e); // This should never happen
- }
- }
-
- /**
- * @return a DH {@link KeyPairGenerator} object initialized for the group described by {@link
- * #DH_G}.
- * @throws InvalidAlgorithmParameterException on some platforms that don't support large DH groups
- */
- @SuppressInsecureCipherModeCheckerPendingReview // b/32143855
- private static KeyPairGenerator getDhKeyGen() throws InvalidAlgorithmParameterException {
- try {
- KeyPairGenerator keygen = KeyPairGenerator.getInstance(DH_ALG);
- keygen.initialize(new DHParameterSpec(DH_P, DH_G, DH_LEN));
- return keygen;
- } catch (NoSuchAlgorithmException e) {
- throw new AssertionError(e); // This should never happen
- }
- }
-
- /**
- * A lightweight shim class to enable the creation of {@link DHPublicKey} and {@link DHPrivateKey}
- * objects that accept arbitrary {@link DHParameterSpec}s -- unfortunately, many platforms do
- * not support using reasonably sized Diffie-Hellman groups any other way. For instance, see
- * <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6521495">Java bug 6521495</a>.
- */
- public abstract static class DHKeyShim {
-
- private BigInteger eitherXorY;
- private DHParameterSpec params;
-
- public DHKeyShim(BigInteger eitherXorY, DHParameterSpec params) {
- this.eitherXorY = eitherXorY;
- this.params = params;
- }
-
- public DHParameterSpec getParams() {
- return params;
- }
-
- public String getAlgorithm() {
- return "DH";
- }
-
- public String getFormat() {
- return null;
- }
-
- public byte[] getEncoded() {
- return null;
- }
-
- public BigInteger getX() {
- return eitherXorY;
- }
-
- public BigInteger getY() {
- return eitherXorY;
- }
- }
-
- /**
- * A simple {@link DHPublicKey} implementation.
- *
- * @see DHKeyShim
- */
- public static class DHPublicKeyShim extends DHKeyShim implements DHPublicKey {
- public DHPublicKeyShim(BigInteger y, DHParameterSpec params) {
- super(y, params);
- }
- }
-
- /**
- * A simple {@link DHPrivateKey} implementation.
- *
- * @see DHKeyShim
- */
- public static class DHPrivateKeyShim extends DHKeyShim implements DHPrivateKey {
- public DHPrivateKeyShim(BigInteger x, DHParameterSpec params) {
- super(x, params);
- }
- }
-
- /**
- * @return true if this platform does not support Elliptic Curve algorithms
- */
- public static boolean isLegacyCryptoRequired() {
- return IS_LEGACY_CRYPTO_REQUIRED;
- }
-
- /**
- * @return true if using the Elliptic Curve key generator fails on this platform
- */
- private static boolean determineIfLegacyCryptoRequired() {
- try {
- getEcKeyGen();
- } catch (Exception e) {
- return true;
- }
- return false;
- }
-}
diff --git a/src/main/java/com/google/security/cryptauth/lib/securemessage/SecureMessageBuilder.java b/src/main/java/com/google/security/cryptauth/lib/securemessage/SecureMessageBuilder.java
deleted file mode 100644
index f1a9464..0000000
--- a/src/main/java/com/google/security/cryptauth/lib/securemessage/SecureMessageBuilder.java
+++ /dev/null
@@ -1,277 +0,0 @@
-// Copyright 2020 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
-//
-// https://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.security.cryptauth.lib.securemessage;
-
-import com.google.protobuf.ByteString;
-import com.google.security.cryptauth.lib.securemessage.CryptoOps.EncType;
-import com.google.security.cryptauth.lib.securemessage.CryptoOps.SigType;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.Header;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.HeaderAndBodyInternal;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.SecureMessage;
-import java.security.InvalidKeyException;
-import java.security.Key;
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
-import java.util.Arrays;
-import javax.annotation.Nullable;
-
-/**
- * Builder for {@link SecureMessage} protos. Can be used to create either signed messages,
- * or "signcrypted" (encrypted then signed) messages that include a tight binding between the
- * ciphertext portion and a verification key identity.
- *
- * @see SecureMessageParser
- */
-public class SecureMessageBuilder {
- private ByteString publicMetadata;
- private ByteString verificationKeyId;
- private ByteString decryptionKeyId;
- /**
- * This data is never sent inside the protobufs, so the builder just saves it as a byte[].
- */
- private byte[] associatedData;
-
- private SecureRandom rng;
-
- public SecureMessageBuilder() {
- reset();
- this.rng = new SecureRandom();
- }
-
- /**
- * Resets this {@link SecureMessageBuilder} instance to a blank configuration (and returns it).
- */
- public SecureMessageBuilder reset() {
- this.publicMetadata = null;
- this.verificationKeyId = null;
- this.decryptionKeyId = null;
- this.associatedData = null;
- return this;
- }
-
- /**
- * Optional metadata to be sent along with the header information in this {@link SecureMessage}.
- * <p>
- * Note that this value will be sent <em>UNENCRYPTED</em> in all cases.
- * <p>
- * Can be used with either cleartext or signcrypted messages, but is intended primarily for use
- * with signcrypted messages.
- */
- public SecureMessageBuilder setPublicMetadata(byte[] publicMetadata) {
- this.publicMetadata = ByteString.copyFrom(publicMetadata);
- return this;
- }
-
- /**
- * The recipient of the {@link SecureMessage} should be able to uniquely determine the correct
- * verification key, given only this value.
- * <p>
- * Can be used with either cleartext or signcrypted messages. Setting this is mandatory for
- * signcrypted messages using a public key {@link SigType}, in order to bind the encrypted
- * body to a specific verification key.
- * <p>
- * Note that this value is sent <em>UNENCRYPTED</em> in all cases.
- */
- public SecureMessageBuilder setVerificationKeyId(byte[] verificationKeyId) {
- this.verificationKeyId = ByteString.copyFrom(verificationKeyId);
- return this;
- }
-
- /**
- * To be used only with {@link #buildSignCryptedMessage(Key, SigType, Key, EncType, byte[])},
- * this value is sent <em>UNENCRYPTED</em> as part of the header. It should be used by the
- * recipient of the {@link SecureMessage} to identify an appropriate key to use for decrypting
- * the message body.
- */
- public SecureMessageBuilder setDecryptionKeyId(byte[] decryptionKeyId) {
- this.decryptionKeyId = ByteString.copyFrom(decryptionKeyId);
- return this;
- }
-
- /**
- * Additional data is "associated" with this {@link SecureMessage}, but will not be sent as
- * part of it. The recipient of the {@link SecureMessage} will need to provide the same data in
- * order to verify the message body. Setting this to {@code null} is equivalent to using an
- * empty array (unlike the behavior of {@code VerificationKeyId} and {@code DecryptionKeyId}).
- * <p>
- * Note that the <em>size</em> (length in bytes) of the associated data will be sent in the
- * <em>UNENCRYPTED</em> header information, even if you are using encryption.
- * <p>
- * If you will be using {@link #buildSignedCleartextMessage(Key, SigType, byte[])}, then anyone
- * observing the {@link SecureMessage} may be able to infer this associated data via an
- * "offline dictionary attack". That is, when no encryption is used, you will not be hiding this
- * data simply because it is not being sent over the wire.
- */
- public SecureMessageBuilder setAssociatedData(@Nullable byte[] associatedData) {
- this.associatedData = associatedData;
- return this;
- }
-
- // @VisibleForTesting
- SecureMessageBuilder setRng(SecureRandom rng) {
- this.rng = rng;
- return this;
- }
-
- /**
- * Generates a signed {@link SecureMessage} with the payload {@code body} left
- * <em>UNENCRYPTED</em>.
- *
- * <p>Note that if you have used {@link #setAssociatedData(byte[])}, the associated data will
- * be subject to offline dictionary attacks if you use a public key {@link SigType}.
- *
- * <p>Doesn't currently support symmetric keys stored in a TPM (since we access the raw key).
- *
- * @see SecureMessageParser#parseSignedCleartextMessage(SecureMessage, Key, SigType)
- */
- public SecureMessage buildSignedCleartextMessage(Key signingKey, SigType sigType, byte[] body)
- throws NoSuchAlgorithmException, InvalidKeyException {
- if ((signingKey == null) || (sigType == null) || (body == null)) {
- throw new NullPointerException();
- }
- if (decryptionKeyId != null) {
- throw new IllegalStateException("Cannot set decryptionKeyId for a cleartext message");
- }
-
- byte[] headerAndBody = serializeHeaderAndBody(
- buildHeader(sigType, EncType.NONE, null).toByteArray(), body);
- return createSignedResult(signingKey, sigType, headerAndBody, associatedData);
- }
-
- /**
- * Generates a signed and encrypted {@link SecureMessage}. If the signature type requires a public
- * key, such as with ECDSA_P256_SHA256, then the caller <em>must</em> set a verification id using
- * the {@link #setVerificationKeyId(byte[])} method. The verification key id will be bound to the
- * encrypted {@code body}, preventing attacks that involve stripping the signature and then
- * re-signing the encrypted {@code body} as if it was originally sent by the attacker.
- *
- * <p>
- * It is safe to re-use one {@link javax.crypto.SecretKey} as both {@code signingKey} and
- * {@code encryptionKey}, even if that key is also used for
- * {@link #buildSignedCleartextMessage(Key, SigType, byte[])}. In fact, the resulting output
- * encoding will be more compact when the same symmetric key is used for both.
- *
- * <p>
- * Note that PublicMetadata and other header fields are left <em>UNENCRYPTED</em>.
- *
- * <p>
- * Doesn't currently support symmetric keys stored in a TPM (since we access the raw key).
- *
- * @param encType <em>must not</em> be set to {@link EncType#NONE}
- * @see SecureMessageParser#parseSignCryptedMessage(SecureMessage, Key, SigType, Key, EncType)
- */
- public SecureMessage buildSignCryptedMessage(
- Key signingKey, SigType sigType, Key encryptionKey, EncType encType, byte[] body)
- throws NoSuchAlgorithmException, InvalidKeyException {
- if ((signingKey == null)
- || (sigType == null)
- || (encryptionKey == null)
- || (encType == null)
- || (body == null)) {
- throw new NullPointerException();
- }
- if (encType == EncType.NONE) {
- throw new IllegalArgumentException(encType + " not supported for encrypted messages");
- }
- if (sigType.isPublicKeyScheme() && (verificationKeyId == null)) {
- throw new IllegalStateException(
- "Must set a verificationKeyId when using public key signature with encryption");
- }
-
- byte[] iv = CryptoOps.generateIv(encType, rng);
- byte[] header = buildHeader(sigType, encType, iv).toByteArray();
-
- // We may or may not need an extra tag in front of the plaintext body
- byte[] taggedBody;
- // We will only sign the associated data when we don't tag the plaintext body
- byte[] associatedDataToBeSigned;
- if (taggedPlaintextRequired(signingKey, sigType, encryptionKey)) {
- // Place a "tag" in front of the the plaintext message containing a digest of the header
- taggedBody = CryptoOps.concat(
- // Digest the header + any associated data, yielding a tag to be encrypted with the body.
- CryptoOps.digest(CryptoOps.concat(header, associatedData)),
- body);
- associatedDataToBeSigned = null; // We already handled any associatedData via the tag
- } else {
- taggedBody = body;
- associatedDataToBeSigned = associatedData;
- }
-
- // Compute the encrypted body, which binds the tag to the message inside the ciphertext
- byte[] encryptedBody = CryptoOps.encrypt(encryptionKey, encType, rng, iv, taggedBody);
-
- byte[] headerAndBody = serializeHeaderAndBody(header, encryptedBody);
- return createSignedResult(signingKey, sigType, headerAndBody, associatedDataToBeSigned);
- }
-
- /**
- * Indicates whether a "tag" is needed next to the plaintext body inside the ciphertext, to
- * prevent the same ciphertext from being reused with someone else's signature on it.
- */
- static boolean taggedPlaintextRequired(Key signingKey, SigType sigType, Key encryptionKey) {
- // We need a tag if different keys are being used to "sign" vs. encrypt
- return sigType.isPublicKeyScheme()
- || !Arrays.equals(signingKey.getEncoded(), encryptionKey.getEncoded());
- }
-
- /**
- * @param iv IV or {@code null} if IV to be left unset in the Header
- */
- private Header buildHeader(SigType sigType, EncType encType, byte[] iv) {
- Header.Builder result = Header.newBuilder()
- .setSignatureScheme(sigType.getSigScheme())
- .setEncryptionScheme(encType.getEncScheme());
- if (verificationKeyId != null) {
- result.setVerificationKeyId(verificationKeyId);
- }
- if (decryptionKeyId != null) {
- result.setDecryptionKeyId(decryptionKeyId);
- }
- if (publicMetadata != null) {
- result.setPublicMetadata(publicMetadata);
- }
- if (associatedData != null) {
- result.setAssociatedDataLength(associatedData.length);
- }
- if (iv != null) {
- result.setIv(ByteString.copyFrom(iv));
- }
- return result.build();
- }
-
- /**
- * @param header a serialized representation of a {@link Header}
- * @param body arbitrary payload data
- * @return a serialized representation of a {@link SecureMessageProto.HeaderAndBody}
- */
- private byte[] serializeHeaderAndBody(byte[] header, byte[] body) {
- return HeaderAndBodyInternal.newBuilder()
- .setHeader(ByteString.copyFrom(header))
- .setBody(ByteString.copyFrom(body))
- .build()
- .toByteArray();
- }
-
- private SecureMessage createSignedResult(
- Key signingKey, SigType sigType, byte[] headerAndBody, @Nullable byte[] associatedData)
- throws NoSuchAlgorithmException, InvalidKeyException {
- byte[] sig =
- CryptoOps.sign(sigType, signingKey, rng, CryptoOps.concat(headerAndBody, associatedData));
- return SecureMessage.newBuilder()
- .setHeaderAndBody(ByteString.copyFrom(headerAndBody))
- .setSignature(ByteString.copyFrom(sig))
- .build();
- }
-}
diff --git a/src/main/java/com/google/security/cryptauth/lib/securemessage/SecureMessageParser.java b/src/main/java/com/google/security/cryptauth/lib/securemessage/SecureMessageParser.java
deleted file mode 100644
index d634d40..0000000
--- a/src/main/java/com/google/security/cryptauth/lib/securemessage/SecureMessageParser.java
+++ /dev/null
@@ -1,270 +0,0 @@
-// Copyright 2020 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
-//
-// https://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.security.cryptauth.lib.securemessage;
-
-import com.google.protobuf.ByteString;
-import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.security.cryptauth.lib.securemessage.CryptoOps.EncType;
-import com.google.security.cryptauth.lib.securemessage.CryptoOps.SigType;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.Header;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.HeaderAndBody;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.HeaderAndBodyInternal;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.SecureMessage;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.Key;
-import java.security.NoSuchAlgorithmException;
-import java.security.SignatureException;
-import javax.annotation.Nullable;
-import javax.crypto.BadPaddingException;
-import javax.crypto.IllegalBlockSizeException;
-
-/**
- * Utility class to parse and verify {@link SecureMessage} protos. Verifies the signature on the
- * message, and decrypts "signcrypted" messages (while simultaneously verifying the signature).
- *
- * @see SecureMessageBuilder
- */
-public class SecureMessageParser {
-
- private SecureMessageParser() {} // Do not instantiate
-
- /**
- * Extracts the {@link Header} component from a {@link SecureMessage} but <em>DOES NOT VERIFY</em>
- * the signature when doing so. Callers should not trust the resulting output until after a
- * subsequent {@code parse*()} call has succeeded.
- *
- * <p>The intention is to allow the caller to determine the type of the protocol message and which
- * keys are in use, prior to attempting to verify (and possibly decrypt) the payload body.
- */
- public static Header getUnverifiedHeader(SecureMessage secmsg)
- throws InvalidProtocolBufferException {
- if (!secmsg.hasHeaderAndBody()) {
- throw new InvalidProtocolBufferException("Missing header and body");
- }
- if (!HeaderAndBody.parseFrom(secmsg.getHeaderAndBody()).hasHeader()) {
- throw new InvalidProtocolBufferException("Missing header");
- }
- Header result = HeaderAndBody.parseFrom(secmsg.getHeaderAndBody()).getHeader();
- // Check that at least a signature scheme was set
- if (!result.hasSignatureScheme()) {
- throw new InvalidProtocolBufferException("Missing header field(s)");
- }
- // Check signature scheme is legal
- try {
- SigType.valueOf(result.getSignatureScheme());
- } catch (IllegalArgumentException e) {
- throw new InvalidProtocolBufferException("Corrupt/unsupported SignatureScheme");
- }
- // Check encryption scheme is legal
- if (result.hasEncryptionScheme()) {
- try {
- EncType.valueOf(result.getEncryptionScheme());
- } catch (IllegalArgumentException e) {
- throw new InvalidProtocolBufferException("Corrupt/unsupported EncryptionScheme");
- }
- }
- return result;
- }
-
- /**
- * Parses a {@link SecureMessage} containing a cleartext payload body, and verifies the signature.
- *
- * @return the parsed {@link HeaderAndBody} pair (which is fully verified)
- * @throws SignatureException if signature verification fails
- * @see SecureMessageBuilder#buildSignedCleartextMessage(Key, SigType, byte[])
- */
- public static HeaderAndBody parseSignedCleartextMessage(
- SecureMessage secmsg, Key verificationKey, SigType sigType)
- throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
- return parseSignedCleartextMessage(secmsg, verificationKey, sigType, null);
- }
-
- /**
- * Parses a {@link SecureMessage} containing a cleartext payload body, and verifies the signature.
- *
- * @param associatedData optional associated data bound to the signature (but not in the message)
- * @return the parsed {@link HeaderAndBody} pair (which is fully verified)
- * @throws SignatureException if signature verification fails
- * @see SecureMessageBuilder#buildSignedCleartextMessage(Key, SigType, byte[])
- */
- public static HeaderAndBody parseSignedCleartextMessage(
- SecureMessage secmsg, Key verificationKey, SigType sigType, @Nullable byte[] associatedData)
- throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
- if ((secmsg == null) || (verificationKey == null) || (sigType == null)) {
- throw new NullPointerException();
- }
- return verifyHeaderAndBody(
- secmsg,
- verificationKey,
- sigType,
- EncType.NONE,
- associatedData,
- false /* suppressAssociatedData is always false for signed cleartext */);
- }
-
- /**
- * Parses a {@link SecureMessage} containing an encrypted payload body, extracting a decryption of
- * the payload body and verifying the signature.
- *
- * @return the parsed {@link HeaderAndBody} pair (which is fully verified and decrypted)
- * @throws SignatureException if signature verification fails
- * @see SecureMessageBuilder#buildSignCryptedMessage(Key, SigType, Key, EncType, byte[])
- */
- public static HeaderAndBody parseSignCryptedMessage(
- SecureMessage secmsg,
- Key verificationKey,
- SigType sigType,
- Key decryptionKey,
- EncType encType)
- throws InvalidKeyException, NoSuchAlgorithmException, SignatureException {
- return parseSignCryptedMessage(secmsg, verificationKey, sigType, decryptionKey, encType, null);
- }
-
- /**
- * Parses a {@link SecureMessage} containing an encrypted payload body, extracting a decryption of
- * the payload body and verifying the signature.
- *
- * @param associatedData optional associated data bound to the signature (but not in the message)
- * @return the parsed {@link HeaderAndBody} pair (which is fully verified and decrypted)
- * @throws SignatureException if signature verification fails
- * @see SecureMessageBuilder#buildSignCryptedMessage(Key, SigType, Key, EncType, byte[])
- */
- public static HeaderAndBody parseSignCryptedMessage(
- SecureMessage secmsg,
- Key verificationKey,
- SigType sigType,
- Key decryptionKey,
- EncType encType,
- @Nullable byte[] associatedData)
- throws InvalidKeyException, NoSuchAlgorithmException, SignatureException {
- if ((secmsg == null)
- || (verificationKey == null)
- || (sigType == null)
- || (decryptionKey == null)
- || (encType == null)) {
- throw new NullPointerException();
- }
- if (encType == EncType.NONE) {
- throw new SignatureException("Not a signcrypted message");
- }
-
- boolean tagRequired =
- SecureMessageBuilder.taggedPlaintextRequired(verificationKey, sigType, decryptionKey);
- HeaderAndBody headerAndEncryptedBody;
- headerAndEncryptedBody = verifyHeaderAndBody(
- secmsg,
- verificationKey,
- sigType,
- encType,
- associatedData,
- tagRequired /* suppressAssociatedData if it is handled by the tag instead */);
-
- byte[] rawDecryptedBody;
- Header header = headerAndEncryptedBody.getHeader();
- if (!header.hasIv()) {
- throw new SignatureException();
- }
- try {
- rawDecryptedBody = CryptoOps.decrypt(
- decryptionKey, encType, header.getIv().toByteArray(),
- headerAndEncryptedBody.getBody().toByteArray());
- } catch (InvalidAlgorithmParameterException e) {
- throw new SignatureException();
- } catch (IllegalBlockSizeException e) {
- throw new SignatureException();
- } catch (BadPaddingException e) {
- throw new SignatureException();
- }
-
- if (!tagRequired) {
- // No tag expected, so we're all done
- return HeaderAndBody.newBuilder(headerAndEncryptedBody)
- .setBody(ByteString.copyFrom(rawDecryptedBody))
- .build();
- }
-
- // Verify the tag that binds the ciphertext to the header, and remove it
- byte[] headerBytes;
- try {
- headerBytes =
- HeaderAndBodyInternal.parseFrom(secmsg.getHeaderAndBody()).getHeader().toByteArray();
- } catch (InvalidProtocolBufferException e) {
- // This shouldn't happen, but throw it up just in case
- throw new SignatureException(e);
- }
- boolean verifiedBinding = false;
- byte[] expectedTag = CryptoOps.digest(CryptoOps.concat(headerBytes, associatedData));
- if (rawDecryptedBody.length >= CryptoOps.DIGEST_LENGTH) {
- byte[] actualTag = CryptoOps.subarray(rawDecryptedBody, 0, CryptoOps.DIGEST_LENGTH);
- if (CryptoOps.constantTimeArrayEquals(actualTag, expectedTag)) {
- verifiedBinding = true;
- }
- }
- if (!verifiedBinding) {
- throw new SignatureException();
- }
-
- int bodyLen = rawDecryptedBody.length - CryptoOps.DIGEST_LENGTH;
- return HeaderAndBody.newBuilder(headerAndEncryptedBody)
- // Remove the tag and set the plaintext body
- .setBody(ByteString.copyFrom(rawDecryptedBody, CryptoOps.DIGEST_LENGTH, bodyLen))
- .build();
- }
-
- private static HeaderAndBody verifyHeaderAndBody(
- SecureMessage secmsg,
- Key verificationKey,
- SigType sigType,
- EncType encType,
- @Nullable byte[] associatedData,
- boolean suppressAssociatedData /* in case it is in the tag instead */)
- throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
- if (!secmsg.hasHeaderAndBody() || !secmsg.hasSignature()) {
- throw new SignatureException("Signature failed verification");
- }
- byte[] signature = secmsg.getSignature().toByteArray();
- byte[] data = secmsg.getHeaderAndBody().toByteArray();
- byte[] signedData = suppressAssociatedData ? data : CryptoOps.concat(data, associatedData);
-
- // Try not to leak the specific reason for verification failures, due to security concerns.
- boolean verified = CryptoOps.verify(verificationKey, sigType, signature, signedData);
- HeaderAndBody result = null;
- try {
- result = HeaderAndBody.parseFrom(secmsg.getHeaderAndBody());
- // Even if declared required, micro proto doesn't throw an exception if fields are not present
- if (!result.hasHeader() || !result.hasBody()) {
- throw new SignatureException("Signature failed verification");
- }
- verified &= (result.getHeader().getSignatureScheme() == sigType.getSigScheme());
- verified &= (result.getHeader().getEncryptionScheme() == encType.getEncScheme());
- // Check that either a decryption operation is expected, or no DecryptionKeyId is set.
- verified &= (encType != EncType.NONE) || !result.getHeader().hasDecryptionKeyId();
- // If encryption was used, check that either we are not using a public key signature or a
- // VerificationKeyId was set (as is required for public key based signature + encryption).
- verified &= (encType == EncType.NONE) || !sigType.isPublicKeyScheme() ||
- result.getHeader().hasVerificationKeyId();
- int associatedDataLength = associatedData == null ? 0 : associatedData.length;
- verified &= (result.getHeader().getAssociatedDataLength() == associatedDataLength);
- } catch (InvalidProtocolBufferException e) {
- verified = false;
- }
-
- if (verified) {
- return result;
- }
- throw new SignatureException("Signature failed verification");
- }
-}
diff --git a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/D2DConnectionContextTest.java b/src/main/javatest/com/google/security/cryptauth/lib/securegcm/D2DConnectionContextTest.java
deleted file mode 100644
index e671e8c..0000000
--- a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/D2DConnectionContextTest.java
+++ /dev/null
@@ -1,568 +0,0 @@
-// Copyright 2020 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
-//
-// https://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.security.cryptauth.lib.securegcm;
-
-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 java.security.SignatureException;
-import java.util.Arrays;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.SecretKeySpec;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-/**
- * Base class for Android compatible tests for {@link D2DConnectionContext} subclasses.
- * Note: We would use a Parameterized test runner to test different versions, but this
- * functionality is not supported by Android tests.
- */
-@RunWith(JUnit4.class)
-public class D2DConnectionContextTest {
- private static final String PING = "ping";
- private static final String PONG = "pong";
-
- // Key is: "initiator_encode_key_for_aes_256"
- private static final SecretKey INITIATOR_ENCODE_KEY = new SecretKeySpec(
- new byte[] {
- (byte) 0x69, (byte) 0x6e, (byte) 0x69, (byte) 0x74, (byte) 0x69, (byte) 0x61, (byte) 0x74,
- (byte) 0x6f, (byte) 0x72, (byte) 0x5f, (byte) 0x65, (byte) 0x6e, (byte) 0x63, (byte) 0x6f,
- (byte) 0x64, (byte) 0x65, (byte) 0x5f, (byte) 0x6b, (byte) 0x65, (byte) 0x79, (byte) 0x5f,
- (byte) 0x66, (byte) 0x6f, (byte) 0x72, (byte) 0x5f, (byte) 0x61, (byte) 0x65, (byte) 0x73,
- (byte) 0x5f, (byte) 0x32, (byte) 0x35, (byte) 0x36
- },
- "AES");
-
- // Key is: "initiator_decode_key_for_aes_256"
- private static final SecretKey INITIATOR_DECODE_KEY = new SecretKeySpec(
- new byte[] {
- (byte) 0x69, (byte) 0x6e, (byte) 0x69, (byte) 0x74, (byte) 0x69, (byte) 0x61, (byte) 0x74,
- (byte) 0x6f, (byte) 0x72, (byte) 0x5f, (byte) 0x64, (byte) 0x65, (byte) 0x63, (byte) 0x6f,
- (byte) 0x64, (byte) 0x65, (byte) 0x5f, (byte) 0x6b, (byte) 0x65, (byte) 0x79, (byte) 0x5f,
- (byte) 0x66, (byte) 0x6f, (byte) 0x72, (byte) 0x5f, (byte) 0x61, (byte) 0x65, (byte) 0x73,
- (byte) 0x5f, (byte) 0x32, (byte) 0x35, (byte) 0x36
- },
- "AES");
-
- private D2DConnectionContext initiatorCtx;
- private D2DConnectionContext responderCtx;
-
- @Before
- public void setUp() throws Exception {
- KeyEncodingTest.installSunEcSecurityProviderIfNecessary();
- }
-
- protected void testPeerToPeerProtocol(int protocolVersion) throws Exception {
-
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- initiatorCtx = createConnectionContext(protocolVersion, true /** isInitiator */);
- responderCtx = createConnectionContext(protocolVersion, false /** isInitiator */);
-
- byte[] pingMessage = initiatorCtx.encodeMessageToPeer(PING);
- // (send message to responder)
-
- // responder
- String messageStr = responderCtx.decodeMessageFromPeerAsString(pingMessage);
- assertEquals(PING, messageStr);
-
- byte[] pongMessage = responderCtx.encodeMessageToPeer(PONG);
- // (send message to initiator)
-
- // initiator
- messageStr = initiatorCtx.decodeMessageFromPeerAsString(pongMessage);
- assertEquals(PONG, messageStr);
-
- // let's make sure there is actually some crypto involved.
- pingMessage = initiatorCtx.encodeMessageToPeer("can you see this?");
- pingMessage[2] = (byte) (pingMessage[2] + 1); // twiddle with the message
- try {
- responderCtx.decodeMessageFromPeerAsString(pingMessage);
- fail("expected exception, but didn't get it");
- } catch (SignatureException expected) {
- assertTrue(expected.getMessage().contains("failed verification"));
- }
-
- // Try and replay the previous encoded message to the initiator (replays should not work).
- try {
- initiatorCtx.decodeMessageFromPeerAsString(pongMessage);
- fail("expected exception, but didn't get it");
- } catch (SignatureException expected) {
- assertTrue(expected.getMessage().contains("sequence"));
- }
-
- assertEquals(protocolVersion, initiatorCtx.getProtocolVersion());
- assertEquals(protocolVersion, responderCtx.getProtocolVersion());
- }
-
- @Test
- public void testPeerToPeerProtocol_V0() throws Exception {
- testPeerToPeerProtocol(D2DConnectionContextV0.PROTOCOL_VERSION);
- }
-
- @Test
- public void testPeerToPeerProtocol_V1() throws Exception {
- testPeerToPeerProtocol(D2DConnectionContextV1.PROTOCOL_VERSION);
- }
-
- protected void testResponderSendsFirst(int protocolVersion) throws Exception {
-
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- initiatorCtx = createConnectionContext(protocolVersion, true /** isInitiator */);
- responderCtx = createConnectionContext(protocolVersion, false /** isInitiator */);
-
- byte[] pongMessage = responderCtx.encodeMessageToPeer(PONG);
- assertEquals(PONG, initiatorCtx.decodeMessageFromPeerAsString(pongMessage));
-
- pongMessage = responderCtx.encodeMessageToPeer(PONG);
- assertEquals(PONG, initiatorCtx.decodeMessageFromPeerAsString(pongMessage));
-
- // for good measure, if the initiator now responds, it should also work:
- byte[] pingMessage = initiatorCtx.encodeMessageToPeer(PING);
- assertEquals(PING, responderCtx.decodeMessageFromPeerAsString(pingMessage));
-
- pingMessage = initiatorCtx.encodeMessageToPeer(PING);
- assertEquals(PING, responderCtx.decodeMessageFromPeerAsString(pingMessage));
-
- pingMessage = initiatorCtx.encodeMessageToPeer(PING);
- assertEquals(PING, responderCtx.decodeMessageFromPeerAsString(pingMessage));
- }
-
- @Test
- public void testResponderSendsFirst_V0() throws Exception {
- testResponderSendsFirst(D2DConnectionContextV0.PROTOCOL_VERSION);
- }
-
- @Test
- public void testResponderSendsFirst_V1() throws Exception {
- testResponderSendsFirst(D2DConnectionContextV1.PROTOCOL_VERSION);
- }
-
- protected void testAssymmetricFlows(int protocolVersion) throws Exception {
-
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- initiatorCtx = createConnectionContext(protocolVersion, true /** isInitiator */);
- responderCtx = createConnectionContext(protocolVersion, false /** isInitiator */);
-
- // Let's test that this still works if one side sends a few messages in a row.
- byte[] pingMessage = initiatorCtx.encodeMessageToPeer(PING);
- assertEquals(PING, responderCtx.decodeMessageFromPeerAsString(pingMessage));
-
- pingMessage = initiatorCtx.encodeMessageToPeer(PING);
- assertEquals(PING, responderCtx.decodeMessageFromPeerAsString(pingMessage));
-
- pingMessage = initiatorCtx.encodeMessageToPeer(PING);
- assertEquals(PING, responderCtx.decodeMessageFromPeerAsString(pingMessage));
-
-
- byte[] pongMessage = responderCtx.encodeMessageToPeer(PONG);
- assertEquals(PONG, initiatorCtx.decodeMessageFromPeerAsString(pongMessage));
-
- pongMessage = responderCtx.encodeMessageToPeer(PONG);
- assertEquals(PONG, initiatorCtx.decodeMessageFromPeerAsString(pongMessage));
- }
-
- @Test
- public void testAssymmetricFlows_V0() throws Exception {
- testAssymmetricFlows(D2DConnectionContextV0.PROTOCOL_VERSION);
- }
-
- @Test
- public void testAssymmetricFlows_V1() throws Exception {
- testAssymmetricFlows(D2DConnectionContextV1.PROTOCOL_VERSION);
- }
-
- public void testErrorWhenResponderResendsMessage(int protocolVersion) throws Exception {
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- initiatorCtx = createConnectionContext(protocolVersion, true /** isInitiator */);
- responderCtx = createConnectionContext(protocolVersion, false /** isInitiator */);
-
- byte[] pongMessage = responderCtx.encodeMessageToPeer(PONG);
- assertEquals(PONG, initiatorCtx.decodeMessageFromPeerAsString(pongMessage));
-
- try {
- // send pongMessage again to the initiator
- initiatorCtx.decodeMessageFromPeerAsString(pongMessage);
- fail("expected exception, but didn't get it");
- } catch (SignatureException expected) {
- assertTrue(expected.getMessage().contains("sequence"));
- }
- }
-
- @Test
- public void testErrorWhenResponderResendsMessage_V0() throws Exception {
- testErrorWhenResponderResendsMessage(D2DConnectionContextV0.PROTOCOL_VERSION);
- }
-
- @Test
- public void testErrorWhenResponderResendsMessage_V1() throws Exception {
- testErrorWhenResponderResendsMessage(D2DConnectionContextV1.PROTOCOL_VERSION);
- }
-
- protected void testErrorWhenResponderEchoesInitiatorMessage(
- int protocolVersion) throws Exception {
- if (KeyEncoding.isLegacyCryptoRequired()) {
- return;
- }
-
- initiatorCtx = createConnectionContext(protocolVersion, true /** isInitiator */);
- responderCtx = createConnectionContext(protocolVersion, false /** isInitiator */);
-
- byte[] pingMessage = initiatorCtx.encodeMessageToPeer(PING);
- assertEquals(PING, responderCtx.decodeMessageFromPeerAsString(pingMessage));
-
- try {
- initiatorCtx.decodeMessageFromPeerAsString(pingMessage);
- fail("expected exception, but didn't get it");
- } catch (SignatureException expected) {
- }
- }
-
- @Test
- public void testErrorWhenResponderEchoesInitiatorMessage_V0() throws Exception {
- testErrorWhenResponderEchoesInitiatorMessage(D2DConnectionContextV0.PROTOCOL_VERSION);
- }
-
- @Test
- public void testErrorWhenResponderEchoesInitiatorMessage_V1() throws Exception {
- testErrorWhenResponderEchoesInitiatorMessage(D2DConnectionContextV1.PROTOCOL_VERSION);
- }
-
- @Test
- public void testErrorUsingV1InitiatorWithV0Responder() throws SignatureException {
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- initiatorCtx = new D2DConnectionContextV1(INITIATOR_ENCODE_KEY, INITIATOR_DECODE_KEY, 1, 1);
- responderCtx = new D2DConnectionContextV0(INITIATOR_DECODE_KEY, 1);
-
- // Decoding the responder's message should succeed, because the decode key and sequence numbers
- // match.
- initiatorCtx.decodeMessageFromPeer(responderCtx.encodeMessageToPeer(PING));
-
- // Responder fails to decodes initiator's encoded message because keys do not match.
- try {
- responderCtx.decodeMessageFromPeer(initiatorCtx.encodeMessageToPeer(PONG));
- fail("Expected verification to fail.");
- } catch (SignatureException e) {
- // Exception expected.
- }
- }
-
- @Test
- public void testErrorWithV0InitiatorV1Responder() throws SignatureException {
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- initiatorCtx = new D2DConnectionContextV0(INITIATOR_ENCODE_KEY, 1);
- responderCtx = new D2DConnectionContextV1(INITIATOR_DECODE_KEY, INITIATOR_ENCODE_KEY, 1, 1);
-
- // Decoding the initiator's message should succeed, because the decode key and sequence numbers
- // match.
- responderCtx.decodeMessageFromPeer(initiatorCtx.encodeMessageToPeer(PING));
-
- // Initiator fails to decodes responder's encoded message because keys do not match.
- try {
- initiatorCtx.decodeMessageFromPeer(responderCtx.encodeMessageToPeer(PONG));
- fail("Expected verification to fail.");
- } catch (SignatureException e) {
- // Exception expected.
- }
- }
-
- protected void testSessionUnique(int protocolVersion) throws Exception {
- // Should be the same (we set them up with the same key and sequence number)
- initiatorCtx = createConnectionContext(protocolVersion, true /** isInitiator */);
- responderCtx = createConnectionContext(protocolVersion, false /** isInitiator */);
- Assert.assertArrayEquals(initiatorCtx.getSessionUnique(), responderCtx.getSessionUnique());
-
- // Change just the key (should not match)
- SecretKey wrongKey = new SecretKeySpec("wrong".getBytes("UTF8"), "AES");
- responderCtx = createConnectionContext(protocolVersion, false, wrongKey, wrongKey, 0, 1);
- assertFalse(Arrays.equals(initiatorCtx.getSessionUnique(), responderCtx.getSessionUnique()));
-
- // Change just the sequence number (should still match)
- responderCtx = createConnectionContext(
- protocolVersion, false, INITIATOR_ENCODE_KEY, INITIATOR_DECODE_KEY, 2, 2);
- Assert.assertArrayEquals(initiatorCtx.getSessionUnique(), responderCtx.getSessionUnique());
- }
-
- @Test
- public void testSessionUnique_V0() throws Exception {
- testSessionUnique(D2DConnectionContextV0.PROTOCOL_VERSION);
- }
-
- @Test
- public void testSessionUnique_V1() throws Exception {
- testSessionUnique(D2DConnectionContextV1.PROTOCOL_VERSION);
- }
-
- @Test
- public void testSessionUniqueValues_V0() throws Exception {
- // The key and the session unique value should match ones in the equivalent test in
- // @link {cs/Nearby/D2DCrypto/Tests/D2DConnectionContextTest.m}
- byte[] key =
- new byte[] {
- (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04, (byte) 0x05, (byte) 0x06, (byte) 0x07,
- (byte) 0x08, (byte) 0x09, (byte) 0x0a, (byte) 0x0b, (byte) 0x0c, (byte) 0x0d, (byte) 0x0e,
- (byte) 0x0f, (byte) 0x10, (byte) 0x11, (byte) 0x12, (byte) 0x13, (byte) 0x14, (byte) 0x15,
- (byte) 0x16, (byte) 0x17, (byte) 0x18, (byte) 0x19, (byte) 0x1a, (byte) 0x1b, (byte) 0x1c,
- (byte) 0x1d, (byte) 0x1e, (byte) 0x1f, (byte) 0x20
- };
- byte[] sessionUnique =
- new byte[] {
- (byte) 0x70, (byte) 0x7a, (byte) 0x17, (byte) 0x27, (byte) 0xa3, (byte) 0x0e, (byte) 0x68,
- (byte) 0x63, (byte) 0x38, (byte) 0xdf, (byte) 0x72, (byte) 0x62, (byte) 0xf4, (byte) 0xb0,
- (byte) 0x41, (byte) 0xac, (byte) 0x75, (byte) 0x8b, (byte) 0xca, (byte) 0x3b, (byte) 0x11,
- (byte) 0xd4, (byte) 0x09, (byte) 0x64, (byte) 0x96, (byte) 0x54, (byte) 0xb4, (byte) 0x9b,
- (byte) 0x43, (byte) 0xe6, (byte) 0x9b, (byte) 0xce
- };
-
- SecretKey secretKey = new SecretKeySpec(key, "AES");
- D2DConnectionContext context = new D2DConnectionContextV0(secretKey, 1);
-
- Assert.assertArrayEquals(context.getSessionUnique(), sessionUnique);
- }
-
- @Test
- public void testSessionUniqueValues_V1_Initiator() throws Exception {
- // The key and the session unique value should match ones in the equivalent test in
- // @link {cs/Nearby/D2DCrypto/Tests/D2DConnectionContextTest.m}
- byte[] sessionUnique =
- new byte[] {
- (byte) 0x91, (byte) 0xc7, (byte) 0xc9, (byte) 0x26, (byte) 0x2c, (byte) 0x17, (byte) 0x8a,
- (byte) 0xa0, (byte) 0x36, (byte) 0x9f, (byte) 0xf2, (byte) 0x05, (byte) 0x20, (byte) 0x98,
- (byte) 0x38, (byte) 0x53, (byte) 0xa5, (byte) 0x46, (byte) 0xab, (byte) 0x3a, (byte) 0x21,
- (byte) 0x3b, (byte) 0x76, (byte) 0x58, (byte) 0x59, (byte) 0x4e, (byte) 0xe7, (byte) 0xe3,
- (byte) 0xc1, (byte) 0x69, (byte) 0x87, (byte) 0xfa
- };
-
- D2DConnectionContext initiatorContext = new D2DConnectionContextV1(
- INITIATOR_ENCODE_KEY, INITIATOR_DECODE_KEY, 0, 1);
- D2DConnectionContext responderContext = new D2DConnectionContextV1(
- INITIATOR_DECODE_KEY, INITIATOR_ENCODE_KEY, 1, 0);
-
- // Both the initiator and responder must be the same.
- Assert.assertArrayEquals(initiatorContext.getSessionUnique(), sessionUnique);
- Assert.assertArrayEquals(responderContext.getSessionUnique(), sessionUnique);
- }
-
- @Test
- public void testSaveSessionV0() throws Exception {
- D2DConnectionContext initiatorCtx = new D2DConnectionContextV0(INITIATOR_ENCODE_KEY, 1);
- D2DConnectionContext responderCtx = new D2DConnectionContextV0(INITIATOR_ENCODE_KEY, 1);
-
- // Save the state
- byte[] initiatorSavedSessionState = initiatorCtx.saveSession();
- byte[] responderSavedSessionState = responderCtx.saveSession();
-
- // Try to rebuild the context
- initiatorCtx = D2DConnectionContext.fromSavedSession(initiatorSavedSessionState);
- responderCtx = D2DConnectionContext.fromSavedSession(responderSavedSessionState);
-
- // Sanity check
- assertEquals(1, initiatorCtx.getSequenceNumberForDecoding());
- assertEquals(1, responderCtx.getSequenceNumberForDecoding());
- Assert.assertArrayEquals(initiatorCtx.getSessionUnique(), responderCtx.getSessionUnique());
-
- // Make sure they can still talk to one another
- assertEquals(PING,
- responderCtx.decodeMessageFromPeerAsString(initiatorCtx.encodeMessageToPeer(PING)));
- assertEquals(PONG,
- initiatorCtx.decodeMessageFromPeerAsString(responderCtx.encodeMessageToPeer(PONG)));
- }
-
- @Test
- public void testSaveSessionV0_negativeSeqNumber() throws Exception {
- D2DConnectionContext initiatorCtx = new D2DConnectionContextV0(INITIATOR_ENCODE_KEY, -5);
-
- // Save the state
- byte[] initiatorSavedSessionState = initiatorCtx.saveSession();
-
- // Try to rebuild the context
- initiatorCtx = D2DConnectionContext.fromSavedSession(initiatorSavedSessionState);
-
- // Sanity check
- assertEquals(-5, initiatorCtx.getSequenceNumberForDecoding());
- }
-
- @Test
- public void testSaveSessionV0_shortKey() throws Exception {
- D2DConnectionContext initiatorCtx = new D2DConnectionContextV0(INITIATOR_ENCODE_KEY, -5);
-
- // Save the state
- byte[] initiatorSavedSessionState = initiatorCtx.saveSession();
-
- // Try to rebuild the context
- try {
- D2DConnectionContext.fromSavedSession(Arrays.copyOf(initiatorSavedSessionState,
- initiatorSavedSessionState.length - 1));
- fail("Expected failure as key is too short");
- } catch (IllegalArgumentException e) {
- // expected
- }
- }
-
- @Test
- public void testSaveSession_unknownProtocolVersion() throws Exception {
- D2DConnectionContext initiatorCtx = new D2DConnectionContextV0(INITIATOR_ENCODE_KEY, -5);
-
- // Save the state
- byte[] initiatorSavedSessionState = initiatorCtx.saveSession();
-
- // Mess with the protocol version
- initiatorSavedSessionState[0] = (byte) 0xff;
-
- // Try to rebuild the context
- try {
- D2DConnectionContext.fromSavedSession(initiatorSavedSessionState);
- fail("Expected failure as 0xff is not a valid protocol version");
- } catch (IllegalArgumentException e) {
- // expected
- }
-
- // Mess with the protocol version in the other direction
- initiatorSavedSessionState[0] = 2;
-
- // Try to rebuild the context
- try {
- D2DConnectionContext.fromSavedSession(initiatorSavedSessionState);
- fail("Expected failure as 2 is not a valid protocol version");
- } catch (IllegalArgumentException e) {
- // expected
- }
- }
-
- @Test
- public void testSaveSessionV1() throws Exception {
- D2DConnectionContext initiatorCtx = new D2DConnectionContextV1(INITIATOR_ENCODE_KEY,
- INITIATOR_DECODE_KEY, 0, 1);
- D2DConnectionContext responderCtx = new D2DConnectionContextV1(INITIATOR_DECODE_KEY,
- INITIATOR_ENCODE_KEY, 1, 0);
-
- // Save the state
- byte[] initiatorSavedSessionState = initiatorCtx.saveSession();
- byte[] responderSavedSessionState = responderCtx.saveSession();
-
- // Try to rebuild the context
- initiatorCtx = D2DConnectionContext.fromSavedSession(initiatorSavedSessionState);
- responderCtx = D2DConnectionContext.fromSavedSession(responderSavedSessionState);
-
- // Sanity check
- assertEquals(1, initiatorCtx.getSequenceNumberForDecoding());
- assertEquals(0, initiatorCtx.getSequenceNumberForEncoding());
- assertEquals(0, responderCtx.getSequenceNumberForDecoding());
- assertEquals(1, responderCtx.getSequenceNumberForEncoding());
- Assert.assertArrayEquals(initiatorCtx.getSessionUnique(), responderCtx.getSessionUnique());
-
- // Make sure they can still talk to one another
- assertEquals(PING,
- responderCtx.decodeMessageFromPeerAsString(initiatorCtx.encodeMessageToPeer(PING)));
- assertEquals(PONG,
- initiatorCtx.decodeMessageFromPeerAsString(responderCtx.encodeMessageToPeer(PONG)));
- }
-
- @Test
- public void testSaveSessionV1_negativeSeqNumbers() throws Exception {
- D2DConnectionContext initiatorCtx = new D2DConnectionContextV1(INITIATOR_ENCODE_KEY,
- INITIATOR_DECODE_KEY, -8, -10);
-
- // Save the state
- byte[] initiatorSavedSessionState = initiatorCtx.saveSession();
-
- // Try to rebuild the context
- initiatorCtx = D2DConnectionContext.fromSavedSession(initiatorSavedSessionState);
-
- // Sanity check
- assertEquals(-10, initiatorCtx.getSequenceNumberForDecoding());
- assertEquals(-8, initiatorCtx.getSequenceNumberForEncoding());
- }
-
- @Test
- public void testSaveSessionV1_tooShort() throws Exception {
- D2DConnectionContext initiatorCtx = new D2DConnectionContextV1(INITIATOR_ENCODE_KEY,
- INITIATOR_DECODE_KEY, -8, -10);
-
- // Save the state
- byte[] initiatorSavedSessionState = initiatorCtx.saveSession();
-
- // Try to rebuild the context
- try {
- D2DConnectionContext.fromSavedSession(
- Arrays.copyOf(initiatorSavedSessionState, initiatorSavedSessionState.length - 1));
- fail("Expected error as saved session is too short");
- } catch (IllegalArgumentException e) {
- // expected
- }
-
- // Sanity check
- assertEquals(-10, initiatorCtx.getSequenceNumberForDecoding());
- assertEquals(-8, initiatorCtx.getSequenceNumberForEncoding());
- }
-
- D2DConnectionContext createConnectionContext(int protocolVersion, boolean isInitiator) {
- return createConnectionContext(
- protocolVersion, isInitiator, INITIATOR_ENCODE_KEY, INITIATOR_DECODE_KEY, 0, 1);
- }
-
- D2DConnectionContext createConnectionContext(
- int protocolVersion, boolean isInitiator,
- SecretKey initiatorEncodeKey, SecretKey initiatorDecodeKey,
- int initiatorSequenceNumber, int responderSequenceNumber) {
- if (protocolVersion == D2DConnectionContextV0.PROTOCOL_VERSION) {
- return new D2DConnectionContextV0(initiatorEncodeKey, responderSequenceNumber);
- } else if (protocolVersion == D2DConnectionContextV1.PROTOCOL_VERSION) {
- return isInitiator
- ? new D2DConnectionContextV1(
- initiatorEncodeKey, initiatorDecodeKey,
- initiatorSequenceNumber, responderSequenceNumber)
- : new D2DConnectionContextV1(
- initiatorDecodeKey, initiatorEncodeKey,
- responderSequenceNumber, initiatorSequenceNumber);
- } else {
- throw new IllegalArgumentException("Unknown version: " + protocolVersion);
- }
- }
-}
diff --git a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/D2DDiffieHellmanKeyExchangeHandshakeTest.java b/src/main/javatest/com/google/security/cryptauth/lib/securegcm/D2DDiffieHellmanKeyExchangeHandshakeTest.java
deleted file mode 100644
index 4de794a..0000000
--- a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/D2DDiffieHellmanKeyExchangeHandshakeTest.java
+++ /dev/null
@@ -1,432 +0,0 @@
-// Copyright 2020 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
-//
-// https://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.security.cryptauth.lib.securegcm;
-
-import com.google.security.cryptauth.lib.securegcm.DeviceToDeviceMessagesProto.InitiatorHello;
-import com.google.security.cryptauth.lib.securegcm.DeviceToDeviceMessagesProto.ResponderHello;
-import com.google.security.cryptauth.lib.securegcm.TransportCryptoOps.Payload;
-import com.google.security.cryptauth.lib.securegcm.TransportCryptoOps.PayloadType;
-import com.google.security.cryptauth.lib.securemessage.PublicKeyProtoUtil;
-import java.nio.charset.Charset;
-import java.security.KeyPair;
-import java.security.PublicKey;
-import java.security.SignatureException;
-import javax.crypto.SecretKey;
-import junit.framework.TestCase;
-import org.junit.Assert;
-
-/**
- * Android compatible tests for the {@link D2DDiffieHellmanKeyExchangeHandshake} class.
- */
-public class D2DDiffieHellmanKeyExchangeHandshakeTest extends TestCase {
-
- private static final byte[] RESPONDER_HELLO_MESSAGE =
- "first payload".getBytes(Charset.forName("UTF-8"));
-
- private static final String PING = "ping";
-
- @Override
- protected void setUp() throws Exception {
- KeyEncodingTest.installSunEcSecurityProviderIfNecessary();
- super.setUp();
- }
-
- public void testHandshakeWithPayload() throws Exception {
-
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- // initiator:
- D2DHandshakeContext initiatorHandshakeContext =
- D2DDiffieHellmanKeyExchangeHandshake.forInitiator();
- assertFalse(initiatorHandshakeContext.canSendPayloadInHandshakeMessage());
- assertFalse(initiatorHandshakeContext.isHandshakeComplete());
- byte[] initiatorHello = initiatorHandshakeContext.getNextHandshakeMessage();
- assertFalse(initiatorHandshakeContext.isHandshakeComplete());
- // (send initiatorHello to responder)
-
- // responder:
- D2DHandshakeContext responderHandshakeContext =
- D2DDiffieHellmanKeyExchangeHandshake.forResponder();
- byte[] payload = responderHandshakeContext.parseHandshakeMessage(initiatorHello);
- assertEquals(0, payload.length);
- assertTrue(responderHandshakeContext.canSendPayloadInHandshakeMessage());
- assertFalse(responderHandshakeContext.isHandshakeComplete());
- byte[] responderHelloAndPayload = responderHandshakeContext.getNextHandshakeMessage(
- RESPONDER_HELLO_MESSAGE);
- assertTrue(responderHandshakeContext.isHandshakeComplete());
- D2DConnectionContext responderCtx = responderHandshakeContext.toConnectionContext();
- // (send responderHelloAndPayload to initiator)
-
- // initiator
- byte[] messageFromPayload =
- initiatorHandshakeContext.parseHandshakeMessage(responderHelloAndPayload);
- Assert.assertArrayEquals(RESPONDER_HELLO_MESSAGE, messageFromPayload);
- assertTrue(initiatorHandshakeContext.isHandshakeComplete());
- D2DConnectionContextV1 initiatorCtx =
- (D2DConnectionContextV1) initiatorHandshakeContext.toConnectionContext();
-
- // Test that that initiator and responder contexts are initialized correctly.
- checkInitializedConnectionContexts(initiatorCtx, responderCtx);
- }
-
- public void testHandshakeWithoutPayload() throws Exception {
-
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- // initiator:
- D2DHandshakeContext initiatorHandshakeContext =
- D2DDiffieHellmanKeyExchangeHandshake.forInitiator();
- byte[] initiatorHello = initiatorHandshakeContext.getNextHandshakeMessage();
- // (send initiatorHello to responder)
-
- // responder:
- D2DHandshakeContext responderHandshakeContext =
- D2DDiffieHellmanKeyExchangeHandshake.forResponder();
- responderHandshakeContext.parseHandshakeMessage(initiatorHello);
- byte[] responderHelloAndPayload = responderHandshakeContext.getNextHandshakeMessage();
- assertTrue(responderHandshakeContext.isHandshakeComplete());
- D2DConnectionContext responderCtx = responderHandshakeContext.toConnectionContext();
- // (send responderHelloAndPayload to initiator)
-
- // initiator
- byte[] messageFromPayload =
- initiatorHandshakeContext.parseHandshakeMessage(responderHelloAndPayload);
- assertEquals(0, messageFromPayload.length);
- assertTrue(initiatorHandshakeContext.isHandshakeComplete());
- D2DConnectionContext initiatorCtx = initiatorHandshakeContext.toConnectionContext();
-
- // Test that that initiator and responder contexts are initialized correctly.
- checkInitializedConnectionContexts(initiatorCtx, responderCtx);
- }
-
- public void testErrorWhenInitiatorOrResponderSendTwice() throws Exception {
-
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- // initiator:
- D2DHandshakeContext initiatorHandshakeContext =
- D2DDiffieHellmanKeyExchangeHandshake.forInitiator();
- byte[] initiatorHello = initiatorHandshakeContext.getNextHandshakeMessage();
- try {
- initiatorHandshakeContext.getNextHandshakeMessage();
- fail("Expected error as initiator has no more initiator messages to send");
- } catch (HandshakeException expected) {
- assertTrue(expected.getMessage().contains("Cannot get next message"));
- }
- // (send initiatorHello to responder)
-
- // responder:
- D2DHandshakeContext responderHandshakeContext =
- D2DDiffieHellmanKeyExchangeHandshake.forResponder();
- responderHandshakeContext.parseHandshakeMessage(initiatorHello);
- responderHandshakeContext.getNextHandshakeMessage();
- try {
- responderHandshakeContext.getNextHandshakeMessage();
- fail("Expected error as initiator has no more responder messages to send");
- } catch (HandshakeException expected) {
- assertTrue(expected.getMessage().contains("Cannot get"));
- }
- }
-
- public void testInitiatorOrResponderFailOnEmptyMessage() throws Exception {
-
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- D2DHandshakeContext handshakeContext =
- D2DDiffieHellmanKeyExchangeHandshake.forInitiator();
- try {
- handshakeContext.parseHandshakeMessage(null);
- fail("Expected to crash on null message");
- } catch (HandshakeException expected) {
- assertTrue(expected.getMessage().contains("short"));
- }
- try {
- handshakeContext.parseHandshakeMessage(new byte[0]);
- fail("Expected to crash on empty message");
- } catch (HandshakeException expected) {
- assertTrue(expected.getMessage().contains("short"));
- }
- }
-
- public void testPrematureConversionToConnection() throws Exception {
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- // initiator:
- D2DHandshakeContext initiatorHandshakeContext =
- D2DDiffieHellmanKeyExchangeHandshake.forInitiator();
- try {
- initiatorHandshakeContext.toConnectionContext();
- fail("Expected to crash: initiator hasn't done anything to deserve full connection");
- } catch (HandshakeException expected) {
- assertTrue(expected.getMessage().contains("not complete"));
- }
-
- byte[] initiatorHello = initiatorHandshakeContext.getNextHandshakeMessage();
- try {
- initiatorHandshakeContext.toConnectionContext();
- fail("Expected to crash: initiator hasn't yet received responder's key");
- } catch (HandshakeException expected) {
- assertTrue(expected.getMessage().contains("not complete"));
- }
- // (send initiatorHello to responder)
-
- // responder:
- D2DHandshakeContext responderHandshakeContext =
- D2DDiffieHellmanKeyExchangeHandshake.forResponder();
- responderHandshakeContext.parseHandshakeMessage(initiatorHello);
- try {
- initiatorHandshakeContext.toConnectionContext();
- fail("Expected to crash: responder hasn't yet send their key");
- } catch (HandshakeException expected) {
- assertTrue(expected.getMessage().contains("not complete"));
- }
- }
-
- public void testCannotReuseHandshakeContext() throws Exception {
-
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- // initiator:
- D2DHandshakeContext initiatorHandshakeContext =
- D2DDiffieHellmanKeyExchangeHandshake.forInitiator();
- byte[] initiatorHello = initiatorHandshakeContext.getNextHandshakeMessage();
- // (send initiatorHello to responder)
-
- // responder:
- D2DHandshakeContext responderHandshakeContext =
- D2DDiffieHellmanKeyExchangeHandshake.forResponder();
- responderHandshakeContext.parseHandshakeMessage(initiatorHello);
- byte[] responderHelloAndPayload = responderHandshakeContext.getNextHandshakeMessage();
- D2DConnectionContext responderCtx = responderHandshakeContext.toConnectionContext();
- // (send responderHelloAndPayload to initiator)
-
- // initiator
- initiatorHandshakeContext.parseHandshakeMessage(responderHelloAndPayload);
- D2DConnectionContext initiatorCtx = initiatorHandshakeContext.toConnectionContext();
-
- // Test that that initiator and responder contexts are initialized correctly.
- checkInitializedConnectionContexts(initiatorCtx, responderCtx);
-
- // Try to get another full context
- try {
- initiatorHandshakeContext.toConnectionContext();
- fail("Expected crash: initiator context has already been used");
- } catch (HandshakeException expected) {
- assertTrue(expected.getMessage().contains("used"));
- }
- try {
- responderHandshakeContext.toConnectionContext();
- fail("Expected crash: responder context has already been used");
- } catch (HandshakeException expected) {
- assertTrue(expected.getMessage().contains("used"));
- }
- }
-
- public void testErrorWhenInitiatorEchosResponderHello() throws Exception {
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- // Initiator echoing back responder's first packet:
- D2DDiffieHellmanKeyExchangeHandshake partialInitiatorContext =
- D2DDiffieHellmanKeyExchangeHandshake.forInitiator();
- byte[] initiatorHello = partialInitiatorContext.getNextHandshakeMessage();
-
- D2DDiffieHellmanKeyExchangeHandshake partialResponderCtx =
- D2DDiffieHellmanKeyExchangeHandshake.forResponder();
- partialResponderCtx.parseHandshakeMessage(initiatorHello);
- byte[] responderHelloAndPayload =
- partialResponderCtx.getNextHandshakeMessage(RESPONDER_HELLO_MESSAGE);
- D2DConnectionContext responderCtx = partialResponderCtx.toConnectionContext();
-
- try {
- // initiator sends responderHelloAndPayload to responder
- responderCtx.decodeMessageFromPeerAsString(responderHelloAndPayload);
- fail("expected exception, but didn't get it");
- } catch (SignatureException expected) {
- assertTrue(expected.getMessage().contains("Signature failed verification"));
- }
- }
-
- public void testErrorWhenInitiatorResendsMessage() throws Exception {
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- // Initiator repeating the same packet twice
- D2DDiffieHellmanKeyExchangeHandshake partialInitiatorContext =
- D2DDiffieHellmanKeyExchangeHandshake.forInitiator();
- byte[] initiatorHello = partialInitiatorContext.getNextHandshakeMessage();
-
- D2DDiffieHellmanKeyExchangeHandshake partialResponderCtx =
- D2DDiffieHellmanKeyExchangeHandshake.forResponder();
- partialResponderCtx.parseHandshakeMessage(initiatorHello);
- byte[] responderHelloAndPayload =
- partialResponderCtx.getNextHandshakeMessage(RESPONDER_HELLO_MESSAGE);
- D2DConnectionContext responderCtx = partialResponderCtx.toConnectionContext();
-
- partialInitiatorContext.parseHandshakeMessage(responderHelloAndPayload);
- D2DConnectionContext initiatorCtx = partialInitiatorContext.toConnectionContext();
-
- byte[] pingMessage = initiatorCtx.encodeMessageToPeer(PING);
- assertEquals(PING, responderCtx.decodeMessageFromPeerAsString(pingMessage));
-
- try {
- // send pingMessage to responder again
- responderCtx.decodeMessageFromPeerAsString(pingMessage);
- fail("expected exception, but didn't get it");
- } catch (SignatureException expected) {
- assertTrue(expected.getMessage().contains("sequence"));
- }
- }
-
- public void testErrorWhenResponderResendsFirstMessage() throws Exception {
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- D2DDiffieHellmanKeyExchangeHandshake partialInitiatorContext =
- D2DDiffieHellmanKeyExchangeHandshake.forInitiator();
- byte[] initiatorHello = partialInitiatorContext.getNextHandshakeMessage();
-
- D2DDiffieHellmanKeyExchangeHandshake partialResponderCtx =
- D2DDiffieHellmanKeyExchangeHandshake.forResponder();
- partialResponderCtx.parseHandshakeMessage(initiatorHello);
- byte[] responderHelloAndPayload =
- partialResponderCtx.getNextHandshakeMessage(RESPONDER_HELLO_MESSAGE);
-
- partialInitiatorContext.parseHandshakeMessage(responderHelloAndPayload);
- D2DConnectionContext initiatorCtx = partialInitiatorContext.toConnectionContext();
-
- try {
- // Send the responderHelloAndPayload again. This time, the initiator will
- // process it as a normal message.
- initiatorCtx.decodeMessageFromPeerAsString(responderHelloAndPayload);
- fail("expected exception, but didn't get it");
- } catch (SignatureException expected) {
- assertTrue(expected.getMessage().contains("wrong message type"));
- }
- }
-
- public void testHandshakeWithInitiatorV1AndResponderV0() throws Exception {
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- // Initialize initiator side.
- D2DHandshakeContext initiatorHandshakeContext =
- D2DDiffieHellmanKeyExchangeHandshake.forInitiator();
- byte[] initiatorHello = initiatorHandshakeContext.getNextHandshakeMessage();
-
- // Set up keys used by the responder.
- PublicKey initiatorPublicKey = PublicKeyProtoUtil.parsePublicKey(
- InitiatorHello.parseFrom(initiatorHello).getPublicDhKey());
- KeyPair responderKeyPair = PublicKeyProtoUtil.generateEcP256KeyPair();
- SecretKey sharedKey =
- EnrollmentCryptoOps.doKeyAgreement(responderKeyPair.getPrivate(), initiatorPublicKey);
-
- // Construct a responder hello message without the version field, whose payload is encrypted
- // with the shared key.
- byte[] responderHello = D2DCryptoOps.signcryptPayload(
- new Payload(
- PayloadType.DEVICE_TO_DEVICE_RESPONDER_HELLO_PAYLOAD,
- D2DConnectionContext.createDeviceToDeviceMessage(new byte[] {}, 1).toByteArray()),
- sharedKey,
- ResponderHello.newBuilder()
- .setPublicDhKey(
- PublicKeyProtoUtil.encodePublicKey(responderKeyPair.getPublic()))
- .build().toByteArray());
-
- // Handle V0 responder hello message.
- initiatorHandshakeContext.parseHandshakeMessage(responderHello);
- D2DConnectionContext initiatorCtx = initiatorHandshakeContext.toConnectionContext();
-
- assertEquals(D2DConnectionContextV0.PROTOCOL_VERSION, initiatorCtx.getProtocolVersion());
- assertEquals(1, initiatorCtx.getSequenceNumberForEncoding());
- assertEquals(1, initiatorCtx.getSequenceNumberForDecoding());
- }
-
- public void testHandshakeWithInitiatorV0AndResponderV1() throws Exception {
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- // Construct an initiator hello message without the version field.
- byte[] initiatorHello = InitiatorHello.newBuilder()
- .setPublicDhKey(PublicKeyProtoUtil.encodePublicKey(
- PublicKeyProtoUtil.generateEcP256KeyPair().getPublic()))
- .build()
- .toByteArray();
-
- // Handle V0 initiator hello message.
- D2DHandshakeContext responderHandshakeContext =
- D2DDiffieHellmanKeyExchangeHandshake.forResponder();
- responderHandshakeContext.parseHandshakeMessage(initiatorHello);
- responderHandshakeContext.getNextHandshakeMessage();
- D2DConnectionContext responderCtx = responderHandshakeContext.toConnectionContext();
-
- assertEquals(D2DConnectionContextV0.PROTOCOL_VERSION, responderCtx.getProtocolVersion());
- assertEquals(1, responderCtx.getSequenceNumberForEncoding());
- assertEquals(1, responderCtx.getSequenceNumberForDecoding());
- }
-
- private void checkInitializedConnectionContexts(
- D2DConnectionContext initiatorCtx, D2DConnectionContext responderCtx) {
- assertNotNull(initiatorCtx);
- assertNotNull(responderCtx);
- assertEquals(D2DConnectionContextV1.PROTOCOL_VERSION, initiatorCtx.getProtocolVersion());
- assertEquals(D2DConnectionContextV1.PROTOCOL_VERSION, responderCtx.getProtocolVersion());
- assertEquals(initiatorCtx.getEncodeKey(), responderCtx.getDecodeKey());
- assertEquals(initiatorCtx.getDecodeKey(), responderCtx.getEncodeKey());
- assertEquals(0, initiatorCtx.getSequenceNumberForEncoding());
- assertEquals(1, initiatorCtx.getSequenceNumberForDecoding());
- assertEquals(1, responderCtx.getSequenceNumberForEncoding());
- assertEquals(0, responderCtx.getSequenceNumberForDecoding());
- }
-}
diff --git a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/Ed25519Test.java b/src/main/javatest/com/google/security/cryptauth/lib/securegcm/Ed25519Test.java
deleted file mode 100644
index 6ae95d8..0000000
--- a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/Ed25519Test.java
+++ /dev/null
@@ -1,195 +0,0 @@
-// Copyright 2020 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
-//
-// https://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.security.cryptauth.lib.securegcm;
-
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertThat;
-
-import com.google.security.cryptauth.lib.securegcm.Ed25519.Ed25519Exception;
-import java.math.BigInteger;
-import junit.framework.TestCase;
-
-/**
- * Android compatible tests for the {@link Ed25519} class.
- */
-public class Ed25519Test extends TestCase {
-
- // Points on the curve
- private static final int HEX_RADIX = 16;
- private static final BigInteger[] KM = new BigInteger[] {
- new BigInteger("1981FB43F103290ECF9772022DB8B19BFAF389057ED91E8486EB368763435925", HEX_RADIX),
- new BigInteger("A714C34F3B588AAC92FD2587884A20964FD351A1F147D5C4BBF5C2F37A77C36", HEX_RADIX)};
- private static final BigInteger[] KN = new BigInteger[] {
- new BigInteger("201A184F47D9A7973891D148E3D1C864D8084547131C2C1CEFB7EEBD26C63567", HEX_RADIX),
- new BigInteger("6DA2D3B18EC4F9AA3B08E39C997CD8BF6E9948FFD4FEFFECAF8DD0B3D648B7E8", HEX_RADIX)};
-
- // Curve prime P
- private static final BigInteger P =
- new BigInteger("7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED", HEX_RADIX);
-
- // Test vectors obtain by multiplying KM by k by manually using the official implementation
- // see: http://ed25519.cr.yp.to/python/ed25519.py
- // k = 2
- private static final BigInteger[] KM_2 = new BigInteger[] {
- new BigInteger("718079972e63c2d62caf0ee93ec6f00337ceaff4e283181c04c4082b1d5e1ecf", HEX_RADIX),
- new BigInteger("143d18d393a8058c8614335bf36bf59364cc7c451db74726b322ce9d0b826d51", HEX_RADIX)
- };
- // k = 3
- private static final BigInteger[] KM_3 = new BigInteger[] {
- new BigInteger("39DA3C92EFC0577586B4D58F4A5C0BF65A6CC8F6BF358F38D70B2E6C28A31E8E", HEX_RADIX),
- new BigInteger("6D194F054B3FC2BE217F6A360BBEC747D2937FCEBD74B67FC3B20ED638ADD670", HEX_RADIX)
- };
- // k = 317698
- private static final BigInteger[] KM_317698 = new BigInteger[] {
- new BigInteger("7945D0ADEB568B16495476E81ADF281F4515439AE835914FBF6CEEAFEB9CD7E8", HEX_RADIX),
- new BigInteger("3631503DCDEBC0BF9BB1FFC3984A8CB52A34FFC2E77E9C19FD896DC6EE64A530", HEX_RADIX)
- };
- // k = P
- private static final BigInteger[] KM_HUGE = new BigInteger[] {
- new BigInteger("530162B05F440E00E219DFD3188524821C860C41FD87B9AC6AF2A283FDD585A1", HEX_RADIX),
- new BigInteger("48385A7D2BB858F3DB7F72E7CDFE218B9CA84DDA8BD64C3775AA43551D974F60", HEX_RADIX)
- };
- // k = P + 10000
- private static final BigInteger[] KM_XRAHUGE = new BigInteger[] {
- new BigInteger("16377E9F5EE2C0F4C70E17AC298EF670700A7CB186EEB0DA10CDD59635000AF8", HEX_RADIX),
- new BigInteger("5BD7921EEE662ACBAC3A96D8B6039D2356F154859FAF41FD2F0D99DF06CD2EAE", HEX_RADIX)
- };
-
- // Helpful constants
- private static final BigInteger ONE = BigInteger.ONE;
- private static final BigInteger ZERO = BigInteger.ZERO;
-
- // Identity element of the group (the zero) in affine and extended representations
- private static final BigInteger[] ID = new BigInteger[] {ZERO, ONE};
- private static final BigInteger[] ID_EX = new BigInteger[] {ZERO, ONE, ONE, ZERO};
-
- public void testValidPoints() throws Exception {
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- // We've got a couple of valid points
- Ed25519.validateAffinePoint(KM);
- Ed25519.validateAffinePoint(KN);
-
- // And a bunch of invalid ones
- try {
- Ed25519.validateAffinePoint(new BigInteger[] {ZERO, ONE});
- fail("Validate point not catching zero x coordinates");
- } catch (Ed25519Exception e) {
- assertThat(e.getMessage(), containsString("positive"));
- }
-
- try {
- Ed25519.validateAffinePoint(new BigInteger[] {ONE, ZERO});
- fail("Validate point not catching zero y coordinates");
- } catch (Ed25519Exception e) {
- assertThat(e.getMessage(), containsString("positive"));
- }
-
- try {
- Ed25519.validateAffinePoint(new BigInteger[] {new BigInteger("-1"), ONE});
- fail("Validate point not catching negative x coordinates");
- } catch (Ed25519Exception e) {
- assertThat(e.getMessage(), containsString("positive"));
- }
-
- try {
- Ed25519.validateAffinePoint(new BigInteger[] {ONE, new BigInteger("-1")});
- fail("Validate point not catching negative y coordinates");
- } catch (Ed25519Exception e) {
- assertThat(e.getMessage(), containsString("positive"));
- }
-
- try {
- Ed25519.validateAffinePoint(new BigInteger[] {ONE, ONE});
- fail("Validate point not catching points that are not on curve");
- } catch (Ed25519Exception e) {
- assertThat(e.getMessage(), containsString("expected curve"));
- }
- }
-
- public void testAffineExtendedConversion() throws Exception {
- BigInteger[] km1 = Ed25519.toAffine(Ed25519.toExtended(KM));
- BigInteger[] kn1 = Ed25519.toAffine(Ed25519.toExtended(KN));
-
- assertArrayEquals(KM, km1);
- assertArrayEquals(KN, kn1);
-
- assertArrayEquals(ID, Ed25519.toAffine(ID_EX));
- assertArrayEquals(ID_EX, Ed25519.toExtended(ID));
- }
-
- public void testRepresentationCheck() throws Exception {
- Ed25519.checkPointIsInAffineRepresentation(KM);
- Ed25519.checkPointIsInExtendedRepresentation(ID_EX);
-
- try {
- Ed25519.checkPointIsInExtendedRepresentation(KM);
- fail("Point is not really in extended representation, expected failure");
- } catch (Ed25519Exception e) {
- assertThat(e.getMessage(), containsString("not in extended"));
- }
-
- try {
- Ed25519.checkPointIsInAffineRepresentation(Ed25519.toExtended(KM));
- fail("Point is not really in affine representation, expected failure");
- } catch (Ed25519Exception e) {
- assertThat(e.getMessage(), containsString("not in affine"));
- }
- }
-
- public void testAddSubtractExtendedPoints() throws Exception {
- // Adding/subtracting identity to/from itself should yield the identity point
- assertArrayEquals(ID, Ed25519.addAffinePoints(ID, ID));
- assertArrayEquals(ID, Ed25519.subtractAffinePoints(ID, ID));
-
- // In fact adding/subtracting the identity point to/from any point should yield that point
- assertArrayEquals(KM, Ed25519.addAffinePoints(KM, ID));
- assertArrayEquals(KM, Ed25519.subtractAffinePoints(KM, ID));
-
- // Subtracting a point from itself should yield the identity element
- assertArrayEquals(ID, Ed25519.subtractAffinePoints(KM, KM));
- assertArrayEquals(ID, Ed25519.subtractAffinePoints(KN, KN));
-
- // Adding and subtracting should yield the same point
- assertArrayEquals(KM, Ed25519.subtractAffinePoints(Ed25519.addAffinePoints(KM, KN), KN));
- assertArrayEquals(KN, Ed25519.subtractAffinePoints(Ed25519.addAffinePoints(KN, KM), KM));
- }
-
- public void testScalarMultiplyExtendedPoints() throws Exception {
- // A point times one is the point itself
- assertArrayEquals(KM, Ed25519.scalarMultiplyAffinePoint(KM, ONE));
- assertArrayEquals(KN, Ed25519.scalarMultiplyAffinePoint(KN, ONE));
-
- // A point times zero is the identity point
- assertArrayEquals(ID, Ed25519.scalarMultiplyAffinePoint(KM, ZERO));
- assertArrayEquals(ID, Ed25519.scalarMultiplyAffinePoint(KN, ZERO));
-
- // The identity times a scalar is the identity
- assertArrayEquals(ID, Ed25519.scalarMultiplyAffinePoint(ID, BigInteger.valueOf(317698)));
-
- // Use test vectors
- assertArrayEquals(KM_2, Ed25519.scalarMultiplyAffinePoint(KM, BigInteger.valueOf(2)));
- assertArrayEquals(KM_3, Ed25519.scalarMultiplyAffinePoint(KM, BigInteger.valueOf(3)));
- assertArrayEquals(KM_317698, Ed25519.scalarMultiplyAffinePoint(KM, BigInteger.valueOf(317698)));
- assertArrayEquals(KM_HUGE, Ed25519.scalarMultiplyAffinePoint(KM, P));
- assertArrayEquals(KM_XRAHUGE,
- Ed25519.scalarMultiplyAffinePoint(KM, P.add(BigInteger.valueOf(10000))));
- }
-}
diff --git a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/EnrollmentCryptoOpsTest.java b/src/main/javatest/com/google/security/cryptauth/lib/securegcm/EnrollmentCryptoOpsTest.java
deleted file mode 100644
index 4437045..0000000
--- a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/EnrollmentCryptoOpsTest.java
+++ /dev/null
@@ -1,134 +0,0 @@
-// Copyright 2020 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
-//
-// https://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.security.cryptauth.lib.securegcm;
-
-import com.google.protobuf.ByteString;
-import com.google.security.cryptauth.lib.securegcm.SecureGcmProto.GcmDeviceInfo;
-import com.google.security.cryptauth.lib.securemessage.PublicKeyProtoUtil;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.GenericPublicKey;
-import java.security.KeyPair;
-import java.security.PublicKey;
-import java.util.Arrays;
-import javax.crypto.SecretKey;
-import junit.framework.TestCase;
-
-/**
- * Android compatible tests for the {@link EnrollmentCryptoOps} class.
- */
-public class EnrollmentCryptoOpsTest extends TestCase {
-
- private static final long DEVICE_ID = 1234567890L;
- private static final byte[] GCM_REGISTRATION_ID = { -0x80, 0, -0x80, 0, -0x80, 0 };
- private static final String DEVICE_MODEL = "TEST DEVICE";
- private static final String LOCALE = "en";
- private static final byte[] SESSION_ID = { 5, 5, 4, 4, 3, 3, 2, 2, 1, 1 };
- private static final String OAUTH_TOKEN = "1/23456etc";
-
- @Override
- protected void setUp() throws Exception {
- KeyEncodingTest.installSunEcSecurityProviderIfNecessary();
- assertEquals(
- PublicKeyProtoUtil.isLegacyCryptoRequired(), KeyEncoding.isLegacyCryptoRequired());
- super.setUp();
- }
-
- @Override
- protected void tearDown() throws Exception {
- KeyEncoding.setSimulateLegacyCrypto(false);
- super.tearDown();
- }
-
-
- public void testSimulatedEnrollment() throws Exception {
- boolean isLegacy = KeyEncoding.isLegacyCryptoRequired();
- // Step 1: Server generates an ephemeral DH key pair, saves the private key, and sends
- // the public key to the client as server_ephemeral_key.
- KeyPair serverEphemeralKeyPair =
- EnrollmentCryptoOps.generateEnrollmentKeyAgreementKeyPair(isLegacy);
- byte[] savedServerPrivateKey =
- KeyEncoding.encodeKeyAgreementPrivateKey(serverEphemeralKeyPair.getPrivate());
- byte[] serverEphemeralKey = KeyEncoding.encodeKeyAgreementPublicKey(
- serverEphemeralKeyPair.getPublic());
-
- // Step 2a: Client generates an ephemeral DH key pair, and completes the DH key exchange
- // to derive the master key.
- KeyPair clientEphemeralKeyPair =
- EnrollmentCryptoOps.generateEnrollmentKeyAgreementKeyPair(isLegacy);
- byte[] clientEphemeralKey = KeyEncoding.encodeKeyAgreementPublicKey(
- clientEphemeralKeyPair.getPublic());
- SecretKey clientMasterKey = EnrollmentCryptoOps.doKeyAgreement(
- clientEphemeralKeyPair.getPrivate(),
- KeyEncoding.parseKeyAgreementPublicKey(serverEphemeralKey));
-
- // Step 2b: Client generates its user key pair, and fills in a GcmDeviceInfo message containing
- // the enrollment request (which includes the user public key).
- KeyPair userKeyPair = isLegacy ? PublicKeyProtoUtil.generateRSA2048KeyPair()
- : PublicKeyProtoUtil.generateEcP256KeyPair();
- GcmDeviceInfo clientInfo = createGcmDeviceInfo(userKeyPair.getPublic(), clientMasterKey);
-
- // Step 2c: Client signcrypts the enrollment request to the server, using a combination of the
- // master key and its user signing key.
- byte[] enrollmentMessage = EnrollmentCryptoOps.encryptEnrollmentMessage(
- clientInfo, clientMasterKey, userKeyPair.getPrivate());
-
-
- // Step 3a: Server receives the client's DH public key and completes the key exchange using
- // the saved DH private key.
- SecretKey serverMasterKey = EnrollmentCryptoOps.doKeyAgreement(
- KeyEncoding.parseKeyAgreementPrivateKey(savedServerPrivateKey, isLegacy),
- KeyEncoding.parseKeyAgreementPublicKey(clientEphemeralKey));
-
- // Step 3b: Server uses the exchanged master key to de-signcrypt the enrollment request
- // (which also provides the user public key in the clear).
- GcmDeviceInfo serverInfo = EnrollmentCryptoOps.decryptEnrollmentMessage(
- enrollmentMessage, serverMasterKey, isLegacy);
-
- // Verify that the server sees the client's original enrollment request
- assertTrue(Arrays.equals(clientInfo.toByteArray(), serverInfo.toByteArray()));
-
- // Confirm that the server can recover a valid user PublicKey from the enrollment
- PublicKey serverUserPublicKey = KeyEncoding.parseUserPublicKey(
- serverInfo.getUserPublicKey().toByteArray());
- assertTrue(serverUserPublicKey.equals(userKeyPair.getPublic()));
- }
-
- public void testSimulatedEnrollmentWithForcedLegacy() throws Exception {
- if (PublicKeyProtoUtil.isLegacyCryptoRequired()) {
- // We already test with legacy in this case
- return;
- }
- KeyEncoding.setSimulateLegacyCrypto(true);
- testSimulatedEnrollment();
- }
-
- private GcmDeviceInfo createGcmDeviceInfo(PublicKey userPublicKey, SecretKey masterKey) {
- // One possible method of generating a key handle:
- GenericPublicKey encodedUserPublicKey = PublicKeyProtoUtil.encodePublicKey(userPublicKey);
- byte[] keyHandle = EnrollmentCryptoOps.sha256(encodedUserPublicKey.toByteArray());
-
- return GcmDeviceInfo.newBuilder()
- .setAndroidDeviceId(DEVICE_ID)
- .setGcmRegistrationId(ByteString.copyFrom(GCM_REGISTRATION_ID))
- .setDeviceMasterKeyHash(
- ByteString.copyFrom(EnrollmentCryptoOps.getMasterKeyHash(masterKey)))
- .setUserPublicKey(ByteString.copyFrom(KeyEncoding.encodeUserPublicKey(userPublicKey)))
- .setDeviceModel(DEVICE_MODEL)
- .setLocale(LOCALE)
- .setKeyHandle(ByteString.copyFrom(keyHandle))
- .setEnrollmentSessionId(ByteString.copyFrom(SESSION_ID))
- .setOauthToken(OAUTH_TOKEN)
- .build();
- }
-}
diff --git a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/KeyEncodingTest.java b/src/main/javatest/com/google/security/cryptauth/lib/securegcm/KeyEncodingTest.java
deleted file mode 100644
index 7012eae..0000000
--- a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/KeyEncodingTest.java
+++ /dev/null
@@ -1,189 +0,0 @@
-// Copyright 2020 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
-//
-// https://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.security.cryptauth.lib.securegcm;
-
-import com.google.security.cryptauth.lib.securemessage.PublicKeyProtoUtil;
-import java.security.Key;
-import java.security.KeyPair;
-import java.security.PrivateKey;
-import java.security.Provider;
-import java.security.PublicKey;
-import java.security.Security;
-import java.security.interfaces.ECPrivateKey;
-import java.security.interfaces.ECPublicKey;
-import java.security.interfaces.RSAPrivateKey;
-import java.security.interfaces.RSAPublicKey;
-import java.security.spec.InvalidKeySpecException;
-import java.util.Arrays;
-import javax.crypto.interfaces.DHPrivateKey;
-import javax.crypto.interfaces.DHPublicKey;
-import junit.framework.TestCase;
-
-/**
- * Android compatible tests for the {@link KeyEncoding} class.
- */
-public class KeyEncodingTest extends TestCase {
- private static final byte[] RAW_KEY_BYTES = {
- 1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
- 1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
- 1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
- 1, 2};
-
- private Boolean isLegacy;
- private KeyPair userKeyPair;
-
- @Override
- protected void setUp() throws Exception {
- installSunEcSecurityProviderIfNecessary();
- isLegacy = PublicKeyProtoUtil.isLegacyCryptoRequired();
- setUserKeyPair();
- super.setUp();
- }
-
- @Override
- protected void tearDown() throws Exception {
- KeyEncoding.setSimulateLegacyCrypto(false);
- isLegacy = PublicKeyProtoUtil.isLegacyCryptoRequired();
- super.tearDown();
- }
-
- private void setUserKeyPair() {
- userKeyPair = isLegacy ? PublicKeyProtoUtil.generateRSA2048KeyPair()
- : PublicKeyProtoUtil.generateEcP256KeyPair();
- }
-
- public void testSimulateLegacyCrypto() {
- if (isLegacy) {
- return; // Nothing to test if we are already stuck in a legacy platform
- }
- assertFalse(KeyEncoding.isLegacyCryptoRequired());
- KeyEncoding.setSimulateLegacyCrypto(true);
- assertTrue(KeyEncoding.isLegacyCryptoRequired());
- }
-
- public void testMasterKeyEncoding() {
- // Require that master keys are encoded/decoded as raw byte arrays
- assertTrue(Arrays.equals(
- RAW_KEY_BYTES,
- KeyEncoding.encodeMasterKey(KeyEncoding.parseMasterKey(RAW_KEY_BYTES))));
- }
-
- public void testUserPublicKeyEncoding() throws InvalidKeySpecException {
- PublicKey pk = userKeyPair.getPublic();
- byte[] encodedPk = KeyEncoding.encodeUserPublicKey(pk);
- PublicKey decodedPk = KeyEncoding.parseUserPublicKey(encodedPk);
- assertKeysEqual(pk, decodedPk);
- }
-
- public void testUserPrivateKeyEncoding() throws InvalidKeySpecException {
- PrivateKey sk = userKeyPair.getPrivate();
- byte[] encodedSk = KeyEncoding.encodeUserPrivateKey(sk);
- PrivateKey decodedSk = KeyEncoding.parseUserPrivateKey(encodedSk, isLegacy);
- assertKeysEqual(sk, decodedSk);
- }
-
- public void testKeyAgreementPublicKeyEncoding() throws InvalidKeySpecException {
- KeyPair clientKeyPair = EnrollmentCryptoOps.generateEnrollmentKeyAgreementKeyPair(isLegacy);
- PublicKey pk = clientKeyPair.getPublic();
- byte[] encodedPk = KeyEncoding.encodeKeyAgreementPublicKey(pk);
- PublicKey decodedPk = KeyEncoding.parseKeyAgreementPublicKey(encodedPk);
- assertKeysEqual(pk, decodedPk);
- }
-
- public void testKeyAgreementPrivateKeyEncoding() throws InvalidKeySpecException {
- KeyPair clientKeyPair = EnrollmentCryptoOps.generateEnrollmentKeyAgreementKeyPair(isLegacy);
- PrivateKey sk = clientKeyPair.getPrivate();
- byte[] encodedSk = KeyEncoding.encodeKeyAgreementPrivateKey(sk);
- PrivateKey decodedSk = KeyEncoding.parseKeyAgreementPrivateKey(encodedSk, isLegacy);
- assertKeysEqual(sk, decodedSk);
- }
-
- public void testEncodingsWithForcedLegacy() throws InvalidKeySpecException {
- if (PublicKeyProtoUtil.isLegacyCryptoRequired()) {
- // We already test with legacy in this case
- return;
- }
- KeyEncoding.setSimulateLegacyCrypto(true);
- isLegacy = true;
- setUserKeyPair();
- testUserPublicKeyEncoding();
- testUserPrivateKeyEncoding();
- testKeyAgreementPublicKeyEncoding();
- testKeyAgreementPrivateKeyEncoding();
- }
-
- public void testSigningPublicKeyEncoding() throws InvalidKeySpecException {
- KeyPair keyPair = PublicKeyProtoUtil.generateEcP256KeyPair();
- PublicKey pk = keyPair.getPublic();
- byte[] encodedPk = KeyEncoding.encodeSigningPublicKey(pk);
- PublicKey decodedPk = KeyEncoding.parseSigningPublicKey(encodedPk);
- assertKeysEqual(pk, decodedPk);
- }
-
- public void testSigningPrivateKeyEncoding() throws InvalidKeySpecException {
- KeyPair keyPair = PublicKeyProtoUtil.generateEcP256KeyPair();
- PrivateKey sk = keyPair.getPrivate();
- byte[] encodedSk = KeyEncoding.encodeSigningPrivateKey(sk);
- PrivateKey decodedSk = KeyEncoding.parseSigningPrivateKey(encodedSk);
- assertKeysEqual(sk, decodedSk);
- }
-
- public void testDeviceSyncPublicKeyEncoding() throws InvalidKeySpecException {
- KeyPair keyPair = PublicKeyProtoUtil.generateEcP256KeyPair();
- PublicKey pk = keyPair.getPublic();
- byte[] encodedPk = KeyEncoding.encodeDeviceSyncGroupPublicKey(pk);
- PublicKey decodedPk = KeyEncoding.parseDeviceSyncGroupPublicKey(encodedPk);
- assertKeysEqual(pk, decodedPk);
- }
-
- void assertKeysEqual(Key a, Key b) {
- if ((a instanceof ECPublicKey)
- || (a instanceof ECPrivateKey)
- || (a instanceof RSAPublicKey)
- || (a instanceof RSAPrivateKey)) {
- assertNotNull(a.getEncoded());
- assertTrue(Arrays.equals(a.getEncoded(), b.getEncoded()));
- }
- if (a instanceof DHPublicKey) {
- DHPublicKey ya = (DHPublicKey) a;
- DHPublicKey yb = (DHPublicKey) b;
- assertEquals(ya.getY(), yb.getY());
- assertEquals(ya.getParams().getG(), yb.getParams().getG());
- assertEquals(ya.getParams().getP(), yb.getParams().getP());
- }
- if (a instanceof DHPrivateKey) {
- DHPrivateKey xa = (DHPrivateKey) a;
- DHPrivateKey xb = (DHPrivateKey) b;
- assertEquals(xa.getX(), xb.getX());
- assertEquals(xa.getParams().getG(), xb.getParams().getG());
- assertEquals(xa.getParams().getP(), xb.getParams().getP());
- }
- }
-
- /**
- * Registers the SunEC security provider if no EC security providers are currently registered.
- */
- // TODO(shabsi): Remove this method when b/7891565 is fixed
- static void installSunEcSecurityProviderIfNecessary() {
- if (Security.getProviders("KeyPairGenerator.EC") == null) {
- try {
- Class<?> providerClass = Class.forName("sun.security.ec.SunEC");
- Security.addProvider((Provider) providerClass.newInstance());
- } catch (Exception e) {
- // SunEC is not available, nothing we can do
- }
- }
- }
-}
diff --git a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/TransportCryptoOpsTest.java b/src/main/javatest/com/google/security/cryptauth/lib/securegcm/TransportCryptoOpsTest.java
deleted file mode 100644
index 9e45c0a..0000000
--- a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/TransportCryptoOpsTest.java
+++ /dev/null
@@ -1,110 +0,0 @@
-// Copyright 2020 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
-//
-// https://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.security.cryptauth.lib.securegcm;
-
-import com.google.security.cryptauth.lib.securegcm.SecureGcmProto.Tickle;
-import com.google.security.cryptauth.lib.securegcm.TransportCryptoOps.Payload;
-import com.google.security.cryptauth.lib.securegcm.TransportCryptoOps.PayloadType;
-import com.google.security.cryptauth.lib.securemessage.PublicKeyProtoUtil;
-import java.security.KeyPair;
-import java.security.PublicKey;
-import java.util.Arrays;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.SecretKeySpec;
-import junit.framework.TestCase;
-
-/**
- * Android compatible tests for the {@link TransportCryptoOps} class.
- */
-public class TransportCryptoOpsTest extends TestCase {
- private static final byte[] KEY_BYTES = {
- 1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
- 1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
- 1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
- 1, 2
- };
- private static final byte[] KEY_HANDLE = { 9 };
-
- private SecretKey masterKey;
-
- @Override
- protected void setUp() throws Exception {
- KeyEncodingTest.installSunEcSecurityProviderIfNecessary();
- masterKey = new SecretKeySpec(KEY_BYTES, "AES");
- super.setUp();
- }
-
- public void testServerMessage() throws Exception {
- long tickleExpiry = 12345L;
- Tickle tickle = Tickle.newBuilder()
- .setExpiryTime(tickleExpiry)
- .build();
-
- // Simulate sending a message
- byte[] signcryptedMessage = TransportCryptoOps.signcryptServerMessage(
- new Payload(PayloadType.TICKLE, tickle.toByteArray()),
- masterKey,
- KEY_HANDLE);
-
- // Simulate the process of receiving the message
- assertTrue(Arrays.equals(KEY_HANDLE, TransportCryptoOps.getKeyHandleFor(signcryptedMessage)));
- Payload received = TransportCryptoOps.verifydecryptServerMessage(signcryptedMessage, masterKey);
- assertEquals(PayloadType.TICKLE, received.getPayloadType());
- Tickle receivedTickle = Tickle.parseFrom(received.getMessage());
- assertEquals(tickleExpiry, receivedTickle.getExpiryTime());
- }
-
- public void testClientMessage() throws Exception {
- if (PublicKeyProtoUtil.isLegacyCryptoRequired()) {
- return; // This test isn't for legacy crypto
- }
- KeyPair userKeyPair = PublicKeyProtoUtil.generateEcP256KeyPair();
- doTestClientMessageWith(userKeyPair);
- }
-
- public void testClientMessageWithLegacyCrypto() throws Exception {
- KeyPair userKeyPair = PublicKeyProtoUtil.generateRSA2048KeyPair();
- doTestClientMessageWith(userKeyPair);
- }
-
- private void doTestClientMessageWith(KeyPair userKeyPair) throws Exception {
- PublicKey userPublicKey = userKeyPair.getPublic();
- // Will use a Tickle for the test message, even though that would normally
- // only be sent from the server to the client
- long tickleExpiry = 12345L;
- Tickle tickle = Tickle.newBuilder()
- .setExpiryTime(tickleExpiry)
- .build();
-
- // Simulate sending a message
- byte[] signcryptedMessage = TransportCryptoOps.signcryptClientMessage(
- new Payload(PayloadType.TICKLE, tickle.toByteArray()),
- userKeyPair,
- masterKey);
-
- // Simulate the process of receiving the message
- byte[] encodedUserPublicKey = TransportCryptoOps.getEncodedUserPublicKeyFor(signcryptedMessage);
- assertTrue(Arrays.equals(KeyEncoding.encodeUserPublicKey(userPublicKey), encodedUserPublicKey));
- userPublicKey = KeyEncoding.parseUserPublicKey(encodedUserPublicKey);
- // At this point the server would have looked up the masterKey for this userPublicKey
-
- Payload received = TransportCryptoOps.verifydecryptClientMessage(
- signcryptedMessage, userPublicKey, masterKey);
-
- assertEquals(PayloadType.TICKLE, received.getPayloadType());
- Tickle receivedTickle = Tickle.parseFrom(received.getMessage());
- assertEquals(tickleExpiry, receivedTickle.getExpiryTime());
- }
-}
diff --git a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/Ukey2CppCompatibilityTest.java b/src/main/javatest/com/google/security/cryptauth/lib/securegcm/Ukey2CppCompatibilityTest.java
deleted file mode 100644
index db319e0..0000000
--- a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/Ukey2CppCompatibilityTest.java
+++ /dev/null
@@ -1,124 +0,0 @@
-// Copyright 2020 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
-//
-// https://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.security.cryptauth.lib.securegcm;
-
-import com.google.security.cryptauth.lib.securegcm.Ukey2Handshake.HandshakeCipher;
-import com.google.security.cryptauth.lib.securegcm.Ukey2ShellCppWrapper.Mode;
-import java.util.Arrays;
-import junit.framework.TestCase;
-
-/**
- * Tests the compatibility between the Java and C++ implementations of the UKEY2 protocol. This
- * integration test executes and talks to a compiled binary exposing the C++ implementation (wrapped
- * by {@link Ukey2ShellCppWrapper}).
- *
- * <p>The C++ implementation is located in //security/cryptauth/lib/securegcm.
- */
-public class Ukey2CppCompatibilityTest extends TestCase {
- private static final int VERIFICATION_STRING_LENGTH = 32;
-
- private static final byte[] sPayload1 = "payload to encrypt1".getBytes();
- private static final byte[] sPayload2 = "payload to encrypt2".getBytes();
-
- /** Tests full handshake with C++ client and Java server. */
- public void testCppClientJavaServer() throws Exception {
- Ukey2ShellCppWrapper cppUkey2Shell =
- new Ukey2ShellCppWrapper(Mode.INITIATOR, VERIFICATION_STRING_LENGTH);
- cppUkey2Shell.startShell();
- Ukey2Handshake javaUkey2Handshake = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
-
- // ClientInit:
- byte[] clientInit = cppUkey2Shell.readHandshakeMessage();
- javaUkey2Handshake.parseHandshakeMessage(clientInit);
-
- // ServerInit:
- byte[] serverInit = javaUkey2Handshake.getNextHandshakeMessage();
- cppUkey2Shell.writeHandshakeMessage(serverInit);
-
- // ClientFinished:
- byte[] clientFinished = cppUkey2Shell.readHandshakeMessage();
- javaUkey2Handshake.parseHandshakeMessage(clientFinished);
-
- // Verification String:
- cppUkey2Shell.confirmAuthString(
- javaUkey2Handshake.getVerificationString(VERIFICATION_STRING_LENGTH));
- javaUkey2Handshake.verifyHandshake();
-
- // Secure channel:
- D2DConnectionContext javaSecureContext = javaUkey2Handshake.toConnectionContext();
-
- // ukey2_shell encodes data:
- byte[] encodedData = cppUkey2Shell.sendEncryptCommand(sPayload1);
- byte[] decodedData = javaSecureContext.decodeMessageFromPeer(encodedData);
- assertTrue(Arrays.equals(sPayload1, decodedData));
-
- // ukey2_shell decodes data:
- encodedData = javaSecureContext.encodeMessageToPeer(sPayload2);
- decodedData = cppUkey2Shell.sendDecryptCommand(encodedData);
- assertTrue(Arrays.equals(sPayload2, decodedData));
-
- // ukey2_shell session unique:
- byte[] localSessionUnique = javaSecureContext.getSessionUnique();
- byte[] remoteSessionUnique = cppUkey2Shell.sendSessionUniqueCommand();
- assertTrue(Arrays.equals(localSessionUnique, remoteSessionUnique));
-
- cppUkey2Shell.stopShell();
- }
-
- /** Tests full handshake with C++ server and Java client. */
- public void testCppServerJavaClient() throws Exception {
- Ukey2ShellCppWrapper cppUkey2Shell =
- new Ukey2ShellCppWrapper(Mode.RESPONDER, VERIFICATION_STRING_LENGTH);
- cppUkey2Shell.startShell();
- Ukey2Handshake javaUkey2Handshake = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
-
- // ClientInit:
- byte[] clientInit = javaUkey2Handshake.getNextHandshakeMessage();
- cppUkey2Shell.writeHandshakeMessage(clientInit);
-
- // ServerInit:
- byte[] serverInit = cppUkey2Shell.readHandshakeMessage();
- javaUkey2Handshake.parseHandshakeMessage(serverInit);
-
- // ClientFinished:
- byte[] clientFinished = javaUkey2Handshake.getNextHandshakeMessage();
- cppUkey2Shell.writeHandshakeMessage(clientFinished);
-
- // Verification String:
- cppUkey2Shell.confirmAuthString(
- javaUkey2Handshake.getVerificationString(VERIFICATION_STRING_LENGTH));
- javaUkey2Handshake.verifyHandshake();
-
- // Secure channel:
- D2DConnectionContext javaSecureContext = javaUkey2Handshake.toConnectionContext();
-
- // ukey2_shell encodes data:
- byte[] encodedData = cppUkey2Shell.sendEncryptCommand(sPayload1);
- byte[] decodedData = javaSecureContext.decodeMessageFromPeer(encodedData);
- assertTrue(Arrays.equals(sPayload1, decodedData));
-
- // ukey2_shell decodes data:
- encodedData = javaSecureContext.encodeMessageToPeer(sPayload2);
- decodedData = cppUkey2Shell.sendDecryptCommand(encodedData);
- assertTrue(Arrays.equals(sPayload2, decodedData));
-
- // ukey2_shell session unique:
- byte[] localSessionUnique = javaSecureContext.getSessionUnique();
- byte[] remoteSessionUnique = cppUkey2Shell.sendSessionUniqueCommand();
- assertTrue(Arrays.equals(localSessionUnique, remoteSessionUnique));
-
- cppUkey2Shell.stopShell();
- }
-}
diff --git a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/Ukey2HandshakeTest.java b/src/main/javatest/com/google/security/cryptauth/lib/securegcm/Ukey2HandshakeTest.java
deleted file mode 100644
index 49e6f30..0000000
--- a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/Ukey2HandshakeTest.java
+++ /dev/null
@@ -1,818 +0,0 @@
-// Copyright 2020 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
-//
-// https://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.security.cryptauth.lib.securegcm;
-
-import com.google.protobuf.ByteString;
-import com.google.security.cryptauth.lib.securegcm.Ukey2Handshake.AlertException;
-import com.google.security.cryptauth.lib.securegcm.Ukey2Handshake.HandshakeCipher;
-import com.google.security.cryptauth.lib.securegcm.Ukey2Handshake.State;
-import com.google.security.cryptauth.lib.securegcm.UkeyProto.Ukey2ClientFinished;
-import com.google.security.cryptauth.lib.securegcm.UkeyProto.Ukey2ClientInit;
-import com.google.security.cryptauth.lib.securegcm.UkeyProto.Ukey2ClientInit.CipherCommitment;
-import com.google.security.cryptauth.lib.securegcm.UkeyProto.Ukey2Message;
-import com.google.security.cryptauth.lib.securegcm.UkeyProto.Ukey2ServerInit;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
-import junit.framework.TestCase;
-import org.junit.Assert;
-
-/**
- * Android compatible tests for the {@link Ukey2Handshake} class.
- */
-public class Ukey2HandshakeTest extends TestCase {
-
- private static final int MAX_AUTH_STRING_LENGTH = 32;
-
- @Override
- protected void setUp() throws Exception {
- KeyEncodingTest.installSunEcSecurityProviderIfNecessary();
- super.setUp();
- }
-
- /**
- * Tests correct use
- */
- public void testHandshake() throws Exception {
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
- Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
- byte[] handshakeMessage;
-
- assertEquals(State.IN_PROGRESS, client.getHandshakeState());
- assertEquals(State.IN_PROGRESS, server.getHandshakeState());
-
- // Message 1 (Client Init)
- handshakeMessage = client.getNextHandshakeMessage();
- server.parseHandshakeMessage(handshakeMessage);
- assertEquals(State.IN_PROGRESS, client.getHandshakeState());
- assertEquals(State.IN_PROGRESS, server.getHandshakeState());
-
- // Message 2 (Server Init)
- handshakeMessage = server.getNextHandshakeMessage();
- client.parseHandshakeMessage(handshakeMessage);
- assertEquals(State.IN_PROGRESS, client.getHandshakeState());
- assertEquals(State.IN_PROGRESS, server.getHandshakeState());
-
- // Message 3 (Client Finish)
- handshakeMessage = client.getNextHandshakeMessage();
- server.parseHandshakeMessage(handshakeMessage);
- assertEquals(State.VERIFICATION_NEEDED, client.getHandshakeState());
- assertEquals(State.VERIFICATION_NEEDED, server.getHandshakeState());
-
- // Get the auth string
- byte[] clientAuthString = client.getVerificationString(MAX_AUTH_STRING_LENGTH);
- byte[] serverAuthString = server.getVerificationString(MAX_AUTH_STRING_LENGTH);
- Assert.assertArrayEquals(clientAuthString, serverAuthString);
- assertEquals(State.VERIFICATION_IN_PROGRESS, client.getHandshakeState());
- assertEquals(State.VERIFICATION_IN_PROGRESS, server.getHandshakeState());
-
- // Verify the auth string
- client.verifyHandshake();
- server.verifyHandshake();
- assertEquals(State.FINISHED, client.getHandshakeState());
- assertEquals(State.FINISHED, server.getHandshakeState());
-
- // Make a context
- D2DConnectionContext clientContext = client.toConnectionContext();
- D2DConnectionContext serverContext = server.toConnectionContext();
- assertContextsCompatible(clientContext, serverContext);
- assertEquals(State.ALREADY_USED, client.getHandshakeState());
- assertEquals(State.ALREADY_USED, server.getHandshakeState());
- }
-
- /**
- * Verify enums for ciphers match the proto values
- */
- public void testCipherEnumValuesCorrect() {
- assertEquals(
- "You added a cipher, but forgot to change the test", 1, HandshakeCipher.values().length);
-
- assertEquals(UkeyProto.Ukey2HandshakeCipher.P256_SHA512,
- HandshakeCipher.P256_SHA512.getValue());
- }
-
- /**
- * Tests incorrect use by callers (client and servers accidentally sending the wrong message at
- * the wrong time)
- */
- public void testHandshakeClientAndServerSendRepeatedOutOfOrderMessages() throws Exception {
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- // Client sends ClientInit (again) instead of ClientFinished
- Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
- Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
- byte[] handshakeMessage = client.getNextHandshakeMessage();
- server.parseHandshakeMessage(handshakeMessage);
- server.getNextHandshakeMessage(); // do this to avoid illegal state
- try {
- server.parseHandshakeMessage(handshakeMessage);
- fail("Expected Alert for client sending ClientInit twice");
- } catch (HandshakeException e) {
- // success
- }
- assertEquals(State.ERROR, server.getHandshakeState());
-
- // Server sends ClientInit back to client instead of ServerInit
- client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
- server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
- handshakeMessage = client.getNextHandshakeMessage();
- try {
- client.parseHandshakeMessage(handshakeMessage);
- fail("Expected Alert for server sending ClientInit back to client");
- } catch (AlertException e) {
- // success
- }
- assertEquals(State.ERROR, client.getHandshakeState());
-
- // Clients sends ServerInit back to client instead of ClientFinished
- client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
- server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
- handshakeMessage = client.getNextHandshakeMessage();
- server.parseHandshakeMessage(handshakeMessage);
- handshakeMessage = server.getNextHandshakeMessage();
- try {
- server.parseHandshakeMessage(handshakeMessage);
- fail("Expected Alert for client sending ServerInit back to server");
- } catch (HandshakeException e) {
- // success
- }
- assertEquals(State.ERROR, server.getHandshakeState());
- }
-
- /**
- * Tests that verification codes are different for different handshake runs. Also tests a full
- * on-path attack.
- */
- public void testVerificationCodeUniqueToSession() throws Exception {
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- // Client 1 and Server 1
- Ukey2Handshake client1 = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
- Ukey2Handshake server1 = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
- byte[] handshakeMessage = client1.getNextHandshakeMessage();
- server1.parseHandshakeMessage(handshakeMessage);
- handshakeMessage = server1.getNextHandshakeMessage();
- client1.parseHandshakeMessage(handshakeMessage);
- handshakeMessage = client1.getNextHandshakeMessage();
- server1.parseHandshakeMessage(handshakeMessage);
- byte[] client1AuthString = client1.getVerificationString(MAX_AUTH_STRING_LENGTH);
- byte[] server1AuthString = server1.getVerificationString(MAX_AUTH_STRING_LENGTH);
- Assert.assertArrayEquals(client1AuthString, server1AuthString);
-
- // Client 2 and Server 2
- Ukey2Handshake client2 = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
- Ukey2Handshake server2 = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
- handshakeMessage = client2.getNextHandshakeMessage();
- server2.parseHandshakeMessage(handshakeMessage);
- handshakeMessage = server2.getNextHandshakeMessage();
- client2.parseHandshakeMessage(handshakeMessage);
- handshakeMessage = client2.getNextHandshakeMessage();
- server2.parseHandshakeMessage(handshakeMessage);
- byte[] client2AuthString = client2.getVerificationString(MAX_AUTH_STRING_LENGTH);
- byte[] server2AuthString = server2.getVerificationString(MAX_AUTH_STRING_LENGTH);
- Assert.assertArrayEquals(client2AuthString, server2AuthString);
-
- // Make sure the verification strings differ
- assertFalse(Arrays.equals(client1AuthString, client2AuthString));
- }
-
- /**
- * Test an attack where the adversary swaps out the public key in the final message (i.e.,
- * commitment doesn't match public key)
- */
- public void testPublicKeyDoesntMatchCommitment() throws Exception {
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- // Run handshake as usual, but stop before sending client finished
- Ukey2Handshake client1 = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
- Ukey2Handshake server1 = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
- byte[] handshakeMessage = client1.getNextHandshakeMessage();
- server1.parseHandshakeMessage(handshakeMessage);
- handshakeMessage = server1.getNextHandshakeMessage();
-
- // Run another handshake and get the final client finished
- Ukey2Handshake client2 = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
- Ukey2Handshake server2 = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
- handshakeMessage = client2.getNextHandshakeMessage();
- server2.parseHandshakeMessage(handshakeMessage);
- handshakeMessage = server2.getNextHandshakeMessage();
- client2.parseHandshakeMessage(handshakeMessage);
- handshakeMessage = client2.getNextHandshakeMessage();
-
- // Now use the client finished from second handshake in first handshake (simulates where an
- // attacker switches out the last message).
- try {
- server1.parseHandshakeMessage(handshakeMessage);
- fail("Expected server to catch mismatched ClientFinished");
- } catch (HandshakeException e) {
- // success
- }
- assertEquals(State.ERROR, server1.getHandshakeState());
-
- // Make sure caller can't actually do anything with the server now that an error has occurred
- try {
- server1.getVerificationString(MAX_AUTH_STRING_LENGTH);
- fail("Server allows operations post error");
- } catch (IllegalStateException e) {
- // success
- }
- try {
- server1.verifyHandshake();
- fail("Server allows operations post error");
- } catch (IllegalStateException e) {
- // success
- }
- try {
- server1.toConnectionContext();
- fail("Server allows operations post error");
- } catch (IllegalStateException e) {
- // success
- }
- }
-
- /**
- * Test commitment having unsupported version
- */
- public void testClientInitUnsupportedVersion() throws Exception {
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- // Get ClientInit and modify the version to be too big
- Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
- Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
- byte[] handshakeMessage = client.getNextHandshakeMessage();
-
- Ukey2Message.Builder message = Ukey2Message.newBuilder(
- Ukey2Message.parseFrom(handshakeMessage));
- Ukey2ClientInit.Builder clientInit =
- Ukey2ClientInit.newBuilder(Ukey2ClientInit.parseFrom(message.getMessageData()));
- clientInit.setVersion(Ukey2Handshake.VERSION + 1);
- message.setMessageData(ByteString.copyFrom(clientInit.build().toByteArray()));
- handshakeMessage = message.build().toByteArray();
-
- try {
- server.parseHandshakeMessage(handshakeMessage);
- fail("Server did not catch unsupported version (too big) in ClientInit");
- } catch (AlertException e) {
- // success
- }
- assertEquals(State.ERROR, server.getHandshakeState());
-
- // Get ClientInit and modify the version to be too big
- client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
- server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
- handshakeMessage = client.getNextHandshakeMessage();
-
- message = Ukey2Message.newBuilder(
- Ukey2Message.parseFrom(handshakeMessage));
- clientInit = Ukey2ClientInit.newBuilder(Ukey2ClientInit.parseFrom(message.getMessageData()));
- clientInit.setVersion(0 /* minimum version is 1 */);
- message.setMessageData(ByteString.copyFrom(clientInit.build().toByteArray()));
- handshakeMessage = message.build().toByteArray();
-
- try {
- server.parseHandshakeMessage(handshakeMessage);
- fail("Server did not catch unsupported version (too small) in ClientInit");
- } catch (AlertException e) {
- // success
- }
- assertEquals(State.ERROR, server.getHandshakeState());
- }
-
- /**
- * Tests that server catches wrong number of random bytes in ClientInit
- */
- public void testWrongNonceLengthInClientInit() throws Exception {
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- // Get ClientInit and modify the nonce
- Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
- Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
- byte[] handshakeMessage = client.getNextHandshakeMessage();
-
- Ukey2Message.Builder message = Ukey2Message.newBuilder(
- Ukey2Message.parseFrom(handshakeMessage));
- Ukey2ClientInit.Builder clientInit =
- Ukey2ClientInit.newBuilder(Ukey2ClientInit.parseFrom(message.getMessageData()));
- clientInit.setRandom(
- ByteString.copyFrom(
- Arrays.copyOf(
- clientInit.getRandom().toByteArray(),
- 31 /* as per go/ukey2, nonces must be 32 bytes long */)));
- message.setMessageData(ByteString.copyFrom(clientInit.build().toByteArray()));
- handshakeMessage = message.build().toByteArray();
-
- try {
- server.parseHandshakeMessage(handshakeMessage);
- fail("Server did not catch nonce being too short in ClientInit");
- } catch (AlertException e) {
- // success
- }
- assertEquals(State.ERROR, server.getHandshakeState());
- }
-
- /**
- * Test that server catches missing commitment in ClientInit message
- */
- public void testServerCatchesMissingCommitmentInClientInit() throws Exception {
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- // Get ClientInit and modify the commitment
- Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
- Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
- byte[] handshakeMessage = client.getNextHandshakeMessage();
-
- Ukey2Message.Builder message = Ukey2Message.newBuilder(
- Ukey2Message.parseFrom(handshakeMessage));
- Ukey2ClientInit clientInit =
- Ukey2ClientInit.newBuilder(Ukey2ClientInit.parseFrom(message.getMessageData()))
- .build();
- Ukey2ClientInit.Builder badClientInit = Ukey2ClientInit.newBuilder()
- .setVersion(clientInit.getVersion())
- .setRandom(clientInit.getRandom());
- for (CipherCommitment commitment : clientInit.getCipherCommitmentsList()) {
- badClientInit.addCipherCommitments(commitment);
- }
-
- message.setMessageData(ByteString.copyFrom(badClientInit.build().toByteArray()));
- handshakeMessage = message.build().toByteArray();
-
- try {
- server.parseHandshakeMessage(handshakeMessage);
- fail("Server did not catch missing commitment in ClientInit");
- } catch (AlertException e) {
- // success
- }
- }
-
- /**
- * Test that client catches invalid version in ServerInit
- */
- public void testServerInitUnsupportedVersion() throws Exception {
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- // Get ServerInit and modify the version to be too big
- Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
- Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
- byte[] handshakeMessage = client.getNextHandshakeMessage();
- server.parseHandshakeMessage(handshakeMessage);
- handshakeMessage = server.getNextHandshakeMessage();
-
- Ukey2Message.Builder message = Ukey2Message.newBuilder(
- Ukey2Message.parseFrom(handshakeMessage));
- Ukey2ServerInit serverInit =
- Ukey2ServerInit.newBuilder(Ukey2ServerInit.parseFrom(message.getMessageData()))
- .setVersion(Ukey2Handshake.VERSION + 1)
- .build();
- message.setMessageData(ByteString.copyFrom(serverInit.toByteArray()));
- handshakeMessage = message.build().toByteArray();
-
- try {
- client.parseHandshakeMessage(handshakeMessage);
- fail("Client did not catch unsupported version (too big) in ServerInit");
- } catch (AlertException e) {
- // success
- }
- assertEquals(State.ERROR, client.getHandshakeState());
-
- // Get ServerInit and modify the version to be too big
- client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
- server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
- handshakeMessage = client.getNextHandshakeMessage();
- server.parseHandshakeMessage(handshakeMessage);
- handshakeMessage = server.getNextHandshakeMessage();
-
- message = Ukey2Message.newBuilder(Ukey2Message.parseFrom(handshakeMessage));
- serverInit =
- Ukey2ServerInit.newBuilder(Ukey2ServerInit.parseFrom(message.getMessageData()))
- .setVersion(0 /* minimum version is 1 */)
- .build();
- message.setMessageData(ByteString.copyFrom(serverInit.toByteArray()));
- handshakeMessage = message.build().toByteArray();
-
- try {
- client.parseHandshakeMessage(handshakeMessage);
- fail("Client did not catch unsupported version (too small) in ServerInit");
- } catch (AlertException e) {
- // success
- }
- assertEquals(State.ERROR, client.getHandshakeState());
- }
-
- /**
- * Tests that client catches wrong number of random bytes in ServerInit
- */
- public void testWrongNonceLengthInServerInit() throws Exception {
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- // Get ServerInit and modify the nonce
- Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
- Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
- byte[] handshakeMessage = client.getNextHandshakeMessage();
- server.parseHandshakeMessage(handshakeMessage);
- handshakeMessage = server.getNextHandshakeMessage();
-
- Ukey2Message.Builder message = Ukey2Message.newBuilder(
- Ukey2Message.parseFrom(handshakeMessage));
- Ukey2ServerInit.Builder serverInitBuilder =
- Ukey2ServerInit.newBuilder(Ukey2ServerInit.parseFrom(message.getMessageData()));
- Ukey2ServerInit serverInit = serverInitBuilder.setRandom(ByteString.copyFrom(Arrays.copyOf(
- serverInitBuilder.getRandom().toByteArray(),
- 31 /* as per go/ukey2, nonces must be 32 bytes long */)))
- .build();
- message.setMessageData(ByteString.copyFrom(serverInit.toByteArray()));
- handshakeMessage = message.build().toByteArray();
-
- try {
- client.parseHandshakeMessage(handshakeMessage);
- fail("Client did not catch nonce being too short in ServerInit");
- } catch (AlertException e) {
- // success
- }
- assertEquals(State.ERROR, client.getHandshakeState());
- }
-
- /**
- * Test that client catches missing or incorrect handshake cipher in serverInit
- */
- public void testMissingOrIncorrectHandshakeCipherInServerInit() throws Exception {
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- // Get ServerInit
- Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
- Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
- byte[] handshakeMessage = client.getNextHandshakeMessage();
- server.parseHandshakeMessage(handshakeMessage);
- handshakeMessage = server.getNextHandshakeMessage();
- Ukey2Message.Builder message = Ukey2Message.newBuilder(
- Ukey2Message.parseFrom(handshakeMessage));
- Ukey2ServerInit serverInit = Ukey2ServerInit.parseFrom(message.getMessageData());
-
- // remove handshake cipher
- Ukey2ServerInit badServerInit = Ukey2ServerInit.newBuilder()
- .setPublicKey(serverInit.getPublicKey())
- .setRandom(serverInit.getRandom())
- .setVersion(serverInit.getVersion())
- .build();
-
- message.setMessageData(ByteString.copyFrom(badServerInit.toByteArray()));
- handshakeMessage = message.build().toByteArray();
-
- try {
- client.parseHandshakeMessage(handshakeMessage);
- fail("Client did not catch missing handshake cipher in ServerInit");
- } catch (AlertException e) {
- // success
- }
- assertEquals(State.ERROR, client.getHandshakeState());
-
- // Get ServerInit
- client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
- server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
- handshakeMessage = client.getNextHandshakeMessage();
- server.parseHandshakeMessage(handshakeMessage);
- handshakeMessage = server.getNextHandshakeMessage();
- message = Ukey2Message.newBuilder(Ukey2Message.parseFrom(handshakeMessage));
- serverInit = Ukey2ServerInit.parseFrom(message.getMessageData());
-
- // put in a bad handshake cipher
- badServerInit = Ukey2ServerInit.newBuilder()
- .setPublicKey(serverInit.getPublicKey())
- .setRandom(serverInit.getRandom())
- .setVersion(serverInit.getVersion())
- .setHandshakeCipher(UkeyProto.Ukey2HandshakeCipher.RESERVED)
- .build();
-
- message.setMessageData(ByteString.copyFrom(badServerInit.toByteArray()));
- handshakeMessage = message.build().toByteArray();
-
- try {
- client.parseHandshakeMessage(handshakeMessage);
- fail("Client did not catch bad handshake cipher in ServerInit");
- } catch (AlertException e) {
- // success
- }
- assertEquals(State.ERROR, client.getHandshakeState());
- }
-
- /**
- * Test that client catches missing or incorrect public key in serverInit
- */
- public void testMissingOrIncorrectPublicKeyInServerInit() throws Exception {
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- // Get ServerInit
- Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
- Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
- byte[] handshakeMessage = client.getNextHandshakeMessage();
- server.parseHandshakeMessage(handshakeMessage);
- handshakeMessage = server.getNextHandshakeMessage();
- Ukey2Message.Builder message = Ukey2Message.newBuilder(
- Ukey2Message.parseFrom(handshakeMessage));
- Ukey2ServerInit serverInit = Ukey2ServerInit.parseFrom(message.getMessageData());
-
- // remove public key
- Ukey2ServerInit badServerInit = Ukey2ServerInit.newBuilder()
- .setRandom(serverInit.getRandom())
- .setVersion(serverInit.getVersion())
- .setHandshakeCipher(serverInit.getHandshakeCipher())
- .build();
-
- message.setMessageData(ByteString.copyFrom(badServerInit.toByteArray()));
- handshakeMessage = message.build().toByteArray();
-
- try {
- client.parseHandshakeMessage(handshakeMessage);
- fail("Client did not catch missing public key in ServerInit");
- } catch (AlertException e) {
- // success
- }
- assertEquals(State.ERROR, client.getHandshakeState());
-
- // Get ServerInit
- client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
- server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
- handshakeMessage = client.getNextHandshakeMessage();
- server.parseHandshakeMessage(handshakeMessage);
- handshakeMessage = server.getNextHandshakeMessage();
- message = Ukey2Message.newBuilder(
- Ukey2Message.parseFrom(handshakeMessage));
- serverInit = Ukey2ServerInit.parseFrom(message.getMessageData());
-
- // put in a bad public key
- badServerInit = Ukey2ServerInit.newBuilder()
- .setPublicKey(ByteString.copyFrom(new byte[] {42, 12, 1}))
- .setRandom(serverInit.getRandom())
- .setVersion(serverInit.getVersion())
- .setHandshakeCipher(serverInit.getHandshakeCipher())
- .build();
-
- message.setMessageData(ByteString.copyFrom(badServerInit.toByteArray()));
- handshakeMessage = message.build().toByteArray();
-
- try {
- client.parseHandshakeMessage(handshakeMessage);
- fail("Client did not catch bad public key in ServerInit");
- } catch (AlertException e) {
- // success
- }
- assertEquals(State.ERROR, client.getHandshakeState());
- }
-
- /**
- * Test that client catches missing or incorrect public key in clientFinished
- */
- public void testMissingOrIncorrectPublicKeyInClientFinished() throws Exception {
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- // Get ClientFinished
- Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
- Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
- byte[] handshakeMessage = client.getNextHandshakeMessage();
- server.parseHandshakeMessage(handshakeMessage);
- handshakeMessage = server.getNextHandshakeMessage();
- client.parseHandshakeMessage(handshakeMessage);
- handshakeMessage = client.getNextHandshakeMessage();
- Ukey2Message.Builder message = Ukey2Message.newBuilder(
- Ukey2Message.parseFrom(handshakeMessage));
-
- // remove public key
- Ukey2ClientFinished.Builder badClientFinished = Ukey2ClientFinished.newBuilder();
-
- message.setMessageData(ByteString.copyFrom(badClientFinished.build().toByteArray()));
- handshakeMessage = message.build().toByteArray();
-
- try {
- server.parseHandshakeMessage(handshakeMessage);
- fail("Server did not catch missing public key in ClientFinished");
- } catch (HandshakeException e) {
- // success
- }
- assertEquals(State.ERROR, server.getHandshakeState());
-
- // Get ClientFinished
- client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
- server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
- handshakeMessage = client.getNextHandshakeMessage();
- server.parseHandshakeMessage(handshakeMessage);
- handshakeMessage = server.getNextHandshakeMessage();
- client.parseHandshakeMessage(handshakeMessage);
- handshakeMessage = client.getNextHandshakeMessage();
- message = Ukey2Message.newBuilder(Ukey2Message.parseFrom(handshakeMessage));
-
- // remove public key
- badClientFinished = Ukey2ClientFinished.newBuilder()
- .setPublicKey(ByteString.copyFrom(new byte[] {42, 12, 1}));
-
- message.setMessageData(ByteString.copyFrom(badClientFinished.build().toByteArray()));
- handshakeMessage = message.build().toByteArray();
-
- try {
- server.parseHandshakeMessage(handshakeMessage);
- fail("Server did not catch bad public key in ClientFinished");
- } catch (HandshakeException e) {
- // success
- }
- assertEquals(State.ERROR, server.getHandshakeState());
- }
-
- /**
- * Tests that items (nonces, commitments, public keys) that should be random are at least
- * different on every run.
- */
- public void testRandomItemsDifferentOnEveryRun() throws Exception {
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- int numberOfRuns = 50;
-
- // Search for collisions
- Set<Integer> commitments = new HashSet<>(numberOfRuns);
- Set<Integer> clientNonces = new HashSet<>(numberOfRuns);
- Set<Integer> serverNonces = new HashSet<>(numberOfRuns);
- Set<Integer> serverPublicKeys = new HashSet<>(numberOfRuns);
- Set<Integer> clientPublicKeys = new HashSet<>(numberOfRuns);
-
- for (int i = 0; i < numberOfRuns; i++) {
- Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
- Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
- byte[] handshakeMessage = client.getNextHandshakeMessage();
- Ukey2Message message = Ukey2Message.parseFrom(handshakeMessage);
- Ukey2ClientInit clientInit = Ukey2ClientInit.parseFrom(message.getMessageData());
-
- server.parseHandshakeMessage(handshakeMessage);
- handshakeMessage = server.getNextHandshakeMessage();
- message = Ukey2Message.parseFrom(handshakeMessage);
- Ukey2ServerInit serverInit = Ukey2ServerInit.parseFrom(message.getMessageData());
-
- client.parseHandshakeMessage(handshakeMessage);
- handshakeMessage = client.getNextHandshakeMessage();
- message = Ukey2Message.parseFrom(handshakeMessage);
- Ukey2ClientFinished clientFinished = Ukey2ClientFinished.parseFrom(message.getMessageData());
-
- // Clean up to save some memory (b/32054837)
- client = null;
- server = null;
- handshakeMessage = null;
- message = null;
- System.gc();
-
- // ClientInit randomness
- Integer nonceHash = Integer.valueOf(Arrays.hashCode(clientInit.getRandom().toByteArray()));
- if (clientNonces.contains(nonceHash) || serverNonces.contains(nonceHash)) {
- fail("Nonce in ClientINit has repeated!");
- }
- clientNonces.add(nonceHash);
-
- Integer commitmentHash = 0;
- for (CipherCommitment commitement : clientInit.getCipherCommitmentsList()) {
- commitmentHash += Arrays.hashCode(commitement.toByteArray());
- }
- if (commitments.contains(nonceHash)) {
- fail("Commitment has repeated!");
- }
- commitments.add(commitmentHash);
-
- // ServerInit randomness
- nonceHash = Integer.valueOf(Arrays.hashCode(serverInit.getRandom().toByteArray()));
- if (serverNonces.contains(nonceHash) || clientNonces.contains(nonceHash)) {
- fail("Nonce in ServerInit repeated!");
- }
- serverNonces.add(nonceHash);
-
- Integer publicKeyHash =
- Integer.valueOf(Arrays.hashCode(serverInit.getPublicKey().toByteArray()));
- if (serverPublicKeys.contains(publicKeyHash) || clientPublicKeys.contains(publicKeyHash)) {
- fail("Public Key in ServerInit repeated!");
- }
- serverPublicKeys.add(publicKeyHash);
-
- // Client Finished randomness
- publicKeyHash = Integer.valueOf(Arrays.hashCode(clientFinished.getPublicKey().toByteArray()));
- if (serverPublicKeys.contains(publicKeyHash) || clientPublicKeys.contains(publicKeyHash)) {
- fail("Public Key in ClientFinished repeated!");
- }
- clientPublicKeys.add(publicKeyHash);
- }
- }
-
- /**
- * Tests that {@link Ukey2Handshake#getVerificationString(int)} enforces sane verification string
- * lengths.
- */
- public void testGetVerificationEnforcesSaneLengths() throws Exception {
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- // Run the protocol
- Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
- Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
- byte[] handshakeMessage = client.getNextHandshakeMessage();
- server.parseHandshakeMessage(handshakeMessage);
- handshakeMessage = server.getNextHandshakeMessage();
- client.parseHandshakeMessage(handshakeMessage);
- handshakeMessage = client.getNextHandshakeMessage();
- server.parseHandshakeMessage(handshakeMessage);
-
- // Try to get too short verification string
- try {
- client.getVerificationString(0);
- fail("Too short verification string allowed");
- } catch (IllegalArgumentException e) {
- // success
- }
-
- // Try to get too long verification string
- try {
- server.getVerificationString(MAX_AUTH_STRING_LENGTH + 1);
- fail("Too long verification string allowed");
- } catch (IllegalArgumentException e) {
- // success
- }
- }
-
- /**
- * Asserts that the given client and server contexts are compatible
- */
- private void assertContextsCompatible(
- D2DConnectionContext clientContext, D2DConnectionContext serverContext) {
- assertNotNull(clientContext);
- assertNotNull(serverContext);
- assertEquals(D2DConnectionContextV1.PROTOCOL_VERSION, clientContext.getProtocolVersion());
- assertEquals(D2DConnectionContextV1.PROTOCOL_VERSION, serverContext.getProtocolVersion());
- assertEquals(clientContext.getEncodeKey(), serverContext.getDecodeKey());
- assertEquals(clientContext.getDecodeKey(), serverContext.getEncodeKey());
- assertFalse(clientContext.getEncodeKey().equals(clientContext.getDecodeKey()));
- assertEquals(0, clientContext.getSequenceNumberForEncoding());
- assertEquals(0, clientContext.getSequenceNumberForDecoding());
- assertEquals(0, serverContext.getSequenceNumberForEncoding());
- assertEquals(0, serverContext.getSequenceNumberForDecoding());
- }
-}
diff --git a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/Ukey2ShellCppWrapper.java b/src/main/javatest/com/google/security/cryptauth/lib/securegcm/Ukey2ShellCppWrapper.java
deleted file mode 100644
index 2b73653..0000000
--- a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/Ukey2ShellCppWrapper.java
+++ /dev/null
@@ -1,342 +0,0 @@
-// Copyright 2020 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
-//
-// https://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.security.cryptauth.lib.securegcm;
-
-import com.google.common.io.BaseEncoding;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.lang.ProcessBuilder.Redirect;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.util.Arrays;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import javax.annotation.Nullable;
-
-/**
- * A wrapper to execute and interact with the //security/cryptauth/lib/securegcm:ukey2_shell binary.
- *
- * <p>This binary is a shell over the C++ implementation of the UKEY2 protocol, so this wrapper is
- * used to test compatibility between the C++ and Java implementations.
- *
- * <p>The ukey2_shell is invoked as follows:
- *
- * <pre>{@code
- * ukey2_shell --mode=<mode> --verification_string_length=<length>
- * }</pre>
- *
- * where {@code mode={initiator, responder}} and {@code verification_string_length} is a positive
- * integer.
- */
-public class Ukey2ShellCppWrapper {
- // The path the the ukey2_shell binary.
- private static final String BINARY_PATH = "build/src/main/cpp/src/securegcm/ukey2_shell";
-
- // The time to wait before timing out a read or write operation to the shell.
- @SuppressWarnings("GoodTime") // TODO(b/147378611): store a java.time.Duration instead
- private static final long IO_TIMEOUT_MILLIS = 5000;
-
- public enum Mode {
- INITIATOR,
- RESPONDER
- }
-
- private final Mode mode;
- private final int verificationStringLength;
- private final ExecutorService executorService;
-
- @Nullable private Process shellProcess;
- private boolean secureContextEstablished;
-
- /**
- * @param mode The mode to run the shell in (initiator or responder).
- * @param verificationStringLength The length of the verification string used in the handshake.
- */
- public Ukey2ShellCppWrapper(Mode mode, int verificationStringLength) {
- this.mode = mode;
- this.verificationStringLength = verificationStringLength;
- this.executorService = Executors.newSingleThreadExecutor();
- }
-
- /**
- * Begins execution of the ukey2_shell binary.
- *
- * @throws IOException
- */
- public void startShell() throws IOException {
- if (shellProcess != null) {
- throw new IllegalStateException("Shell already started.");
- }
-
- String modeArg = "--mode=" + getModeString();
- String verificationStringLengthArg = "--verification_string_length=" + verificationStringLength;
-
- final ProcessBuilder builder =
- new ProcessBuilder(BINARY_PATH, modeArg, verificationStringLengthArg);
-
- // Merge the shell's stderr with the stderr of the current process.
- builder.redirectError(Redirect.INHERIT);
-
- shellProcess = builder.start();
- }
-
- /**
- * Stops execution of the ukey2_shell binary.
- *
- * @throws IOException
- */
- public void stopShell() {
- if (shellProcess == null) {
- throw new IllegalStateException("Shell not started.");
- }
- shellProcess.destroy();
- }
-
- /**
- * @return the handshake message read from the shell.
- * @throws IOException
- */
- public byte[] readHandshakeMessage() throws IOException {
- return readFrameWithTimeout();
- }
-
- /**
- * Sends the handshake message to the shell.
- *
- * @param message
- * @throws IOException
- */
- public void writeHandshakeMessage(byte[] message) throws IOException {
- writeFrameWithTimeout(message);
- }
-
- /**
- * Reads the auth string from the shell and compares it with {@code authString}. If verification
- * succeeds, then write "ok" back as a confirmation.
- *
- * @param authString the auth string to compare to.
- * @throws IOException
- */
- public void confirmAuthString(byte[] authString) throws IOException {
- byte[] shellAuthString = readFrameWithTimeout();
- if (!Arrays.equals(authString, shellAuthString)) {
- throw new IOException(
- String.format(
- "Unable to verify auth string: 0x%s != 0x%s",
- BaseEncoding.base16().encode(authString),
- BaseEncoding.base16().encode(shellAuthString)));
- }
- writeFrameWithTimeout("ok".getBytes());
- secureContextEstablished = true;
- }
-
- /**
- * Sends {@code payload} to be encrypted by the shell. This function can only be called after a
- * handshake is performed and a secure context established.
- *
- * @param payload the data to be encrypted.
- * @return the encrypted message returned by the shell.
- * @throws IOException
- */
- public byte[] sendEncryptCommand(byte[] payload) throws IOException {
- writeFrameWithTimeout(createExpression("encrypt", payload));
- return readFrameWithTimeout();
- }
-
- /**
- * Sends {@code message} to be decrypted by the shell. This function can only be called after a
- * handshake is performed and a secure context established.
- *
- * @param message the data to be decrypted.
- * @return the decrypted payload returned by the shell.
- * @throws IOException
- */
- public byte[] sendDecryptCommand(byte[] message) throws IOException {
- writeFrameWithTimeout(createExpression("decrypt", message));
- return readFrameWithTimeout();
- }
-
- /**
- * Requests the session unique value from the shell. This function can only be called after a
- * handshake is performed and a secure context established.
- *
- * @return the session unique value returned by the shell.
- * @throws IOException
- */
- public byte[] sendSessionUniqueCommand() throws IOException {
- writeFrameWithTimeout(createExpression("session_unique", null));
- return readFrameWithTimeout();
- }
-
- /**
- * Reads a frame from the shell's stdout with a timeout.
- *
- * @return The contents of the frame.
- * @throws IOException
- */
- private byte[] readFrameWithTimeout() throws IOException {
- Future<byte[]> future =
- executorService.submit(
- new Callable<byte[]>() {
- @Override
- public byte[] call() throws Exception {
- return readFrame();
- }
- });
-
- try {
- return future.get(IO_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
- } catch (InterruptedException | ExecutionException | TimeoutException e) {
- throw new IOException(e);
- }
- }
-
- /**
- * Writes a frame to the shell's stdin with a timeout.
- *
- * @param contents the contents of the frame.
- * @throws IOException
- */
- private void writeFrameWithTimeout(final byte[] contents) throws IOException {
- Future<?> future =
- executorService.submit(
- new Runnable() {
- @Override
- public void run() {
- try {
- writeFrame(contents);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
- });
-
- try {
- future.get(IO_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
- } catch (InterruptedException | ExecutionException | TimeoutException e) {
- throw new IOException(e);
- }
- }
-
- /**
- * Reads a frame from the shell's stdout, which has the format:
- *
- * <pre>{@code
- * +---------------------+-----------------+
- * | 4-bytes | |length| bytes |
- * +---------------------+-----------------+
- * | (unsigned) length | contents |
- * +---------------------+-----------------+
- * }</pre>
- *
- * @return the contents that were read
- * @throws IOException
- */
- private byte[] readFrame() throws IOException {
- if (shellProcess == null) {
- throw new IllegalStateException("Shell not started.");
- }
-
- InputStream inputStream = shellProcess.getInputStream();
- byte[] lengthBytes = new byte[4];
- if (inputStream.read(lengthBytes) != lengthBytes.length) {
- throw new IOException("Failed to read length.");
- }
-
- int length = ByteBuffer.wrap(lengthBytes).order(ByteOrder.BIG_ENDIAN).getInt();
- if (length < 0) {
- throw new IOException("Length too large: " + Arrays.toString(lengthBytes));
- }
-
- byte[] contents = new byte[length];
- int bytesRead = inputStream.read(contents);
- if (bytesRead != length) {
- throw new IOException("Failed to read entire contents: " + bytesRead + " != " + length);
- }
-
- return contents;
- }
-
- /**
- * Writes a frame to the shell's stdin, which has the format:
- *
- * <pre>{@code
- * +---------------------+-----------------+
- * | 4-bytes | |length| bytes |
- * +---------------------+-----------------+
- * | (unsigned) length | contents |
- * +---------------------+-----------------+
- * }</pre>
- *
- * @param contents the contents to send.
- * @throws IOException
- */
- private void writeFrame(byte[] contents) throws IOException {
- if (shellProcess == null) {
- throw new IllegalStateException("Shell not started.");
- }
-
- // The length is big-endian encoded, network byte order.
- long length = contents.length;
- byte[] lengthBytes = new byte[4];
- lengthBytes[0] = (byte) (length >> 32 & 0xFF);
- lengthBytes[1] = (byte) (length >> 16 & 0xFF);
- lengthBytes[2] = (byte) (length >> 8 & 0xFF);
- lengthBytes[3] = (byte) (length >> 0 & 0xFF);
-
- OutputStream outputStream = shellProcess.getOutputStream();
- outputStream.write(lengthBytes);
- outputStream.write(contents);
- outputStream.flush();
- }
-
- /**
- * Creates an expression to be processed when a secure connection is established, after the
- * handshake is done.
- *
- * @param command The command to send.
- * @param argument The argument of the command. Can be null.
- * @return the expression that can be sent to the shell.
- * @throws IOException.
- */
- private byte[] createExpression(String command, @Nullable byte[] argument) throws IOException {
- ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
- outputStream.write(command.getBytes());
- outputStream.write(" ".getBytes());
- if (argument != null) {
- outputStream.write(argument);
- }
- return outputStream.toByteArray();
- }
-
- /** @return the mode string to use in the argument to start the ukey2_shell process. */
- private String getModeString() {
- switch (mode) {
- case INITIATOR:
- return "initiator";
- case RESPONDER:
- return "responder";
- default:
- throw new IllegalArgumentException("Uknown mode " + mode);
- }
- }
-}
diff --git a/src/main/javatest/com/google/security/cryptauth/lib/securemessage/CryptoOpsTest.java b/src/main/javatest/com/google/security/cryptauth/lib/securemessage/CryptoOpsTest.java
deleted file mode 100644
index 65fa094..0000000
--- a/src/main/javatest/com/google/security/cryptauth/lib/securemessage/CryptoOpsTest.java
+++ /dev/null
@@ -1,172 +0,0 @@
-// Copyright 2020 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
-//
-// https://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.security.cryptauth.lib.securemessage;
-
-import static org.junit.Assert.assertThrows;
-
-import com.google.security.cryptauth.lib.securemessage.CryptoOps.EncType;
-import com.google.security.cryptauth.lib.securemessage.CryptoOps.SigType;
-import java.util.Arrays;
-import javax.crypto.KeyGenerator;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.SecretKeySpec;
-import junit.framework.TestCase;
-
-/**
- * Unit tests for the CryptoOps class
- */
-public class CryptoOpsTest extends TestCase {
-
- /** HKDF Test Case 1 IKM from RFC 5869 */
- private static final byte[] HKDF_CASE1_IKM = {
- 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
- 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
- 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
- 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
- 0x0b, 0x0b
- };
-
- /** HKDF Test Case 1 salt from RFC 5869 */
- private static final byte[] HKDF_CASE1_SALT = {
- 0x00, 0x01, 0x02, 0x03, 0x04,
- 0x05, 0x06, 0x07, 0x08, 0x09,
- 0x0a, 0x0b, 0x0c
- };
-
- /** HKDF Test Case 1 info from RFC 5869 */
- private static final byte[] HKDF_CASE1_INFO = {
- (byte) 0xf0, (byte) 0xf1, (byte) 0xf2, (byte) 0xf3, (byte) 0xf4,
- (byte) 0xf5, (byte) 0xf6, (byte) 0xf7, (byte) 0xf8, (byte) 0xf9
- };
-
- /** First 32 bytes of HKDF Test Case 1 OKM (output) from RFC 5869 */
- private static final byte[] HKDF_CASE1_OKM = {
- (byte) 0x3c, (byte) 0xb2, (byte) 0x5f, (byte) 0x25, (byte) 0xfa,
- (byte) 0xac, (byte) 0xd5, (byte) 0x7a, (byte) 0x90, (byte) 0x43,
- (byte) 0x4f, (byte) 0x64, (byte) 0xd0, (byte) 0x36, (byte) 0x2f,
- (byte) 0x2a, (byte) 0x2d, (byte) 0x2d, (byte) 0x0a, (byte) 0x90,
- (byte) 0xcf, (byte) 0x1a, (byte) 0x5a, (byte) 0x4c, (byte) 0x5d,
- (byte) 0xb0, (byte) 0x2d, (byte) 0x56, (byte) 0xec, (byte) 0xc4,
- (byte) 0xc5, (byte) 0xbf, (byte) 0x34, (byte) 0x00, (byte) 0x72,
- (byte) 0x08, (byte) 0xd5, (byte) 0xb8, (byte) 0x87, (byte) 0x18,
- (byte) 0x58, (byte) 0x65
- };
-
- private SecretKey aesKey1;
- private SecretKey aesKey2;
-
- @Override
- protected void setUp() throws Exception {
- KeyGenerator aesKeygen = KeyGenerator.getInstance("AES");
- aesKeygen.init(256);
- aesKey1 = aesKeygen.generateKey();
- aesKey2 = aesKeygen.generateKey();
- super.setUp();
- }
-
- public void testNoPurposeConflicts() {
- // Ensure that signature algorithms and encryption algorithms are not given identical purposes
- // (this prevents confusion of derived keys).
- for (SigType sigType : SigType.values()) {
- for (EncType encType : EncType.values()) {
- assertFalse(CryptoOps.getPurpose(sigType).equals(CryptoOps.getPurpose(encType)));
- }
- }
- }
-
- public void testDeriveAes256KeyFor() throws Exception {
- // Test that deriving with the same key and purpose twice is deterministic
- assertTrue(Arrays.equals(CryptoOps.deriveAes256KeyFor(aesKey1, "A").getEncoded(),
- CryptoOps.deriveAes256KeyFor(aesKey1, "A").getEncoded()));
- // Test that derived keys with different purposes differ
- assertFalse(Arrays.equals(CryptoOps.deriveAes256KeyFor(aesKey1, "A").getEncoded(),
- CryptoOps.deriveAes256KeyFor(aesKey1, "B").getEncoded()));
- // Test that derived keys with the same purpose but different master keys differ
- assertFalse(Arrays.equals(CryptoOps.deriveAes256KeyFor(aesKey1, "A").getEncoded(),
- CryptoOps.deriveAes256KeyFor(aesKey2, "A").getEncoded()));
- }
-
- public void testHkdf() throws Exception {
- SecretKey inputKey = new SecretKeySpec(HKDF_CASE1_IKM, "AES");
- byte[] result = CryptoOps.hkdf(inputKey, HKDF_CASE1_SALT, HKDF_CASE1_INFO);
- byte[] expectedResult = Arrays.copyOf(HKDF_CASE1_OKM, 32);
- assertTrue(Arrays.equals(result, expectedResult));
- }
-
- public void testHkdfLongOutput() throws Exception {
- SecretKey inputKey = new SecretKeySpec(HKDF_CASE1_IKM, "AES");
- byte[] result = CryptoOps.hkdf(inputKey, HKDF_CASE1_SALT, HKDF_CASE1_INFO, 42);
- byte[] expectedResult = Arrays.copyOf(HKDF_CASE1_OKM, 42);
- assertTrue(Arrays.equals(result, expectedResult));
- }
-
- public void testHkdfShortOutput() throws Exception {
- SecretKey inputKey = new SecretKeySpec(HKDF_CASE1_IKM, "AES");
- byte[] result = CryptoOps.hkdf(inputKey, HKDF_CASE1_SALT, HKDF_CASE1_INFO, 12);
- byte[] expectedResult = Arrays.copyOf(HKDF_CASE1_OKM, 12);
- assertTrue(Arrays.equals(result, expectedResult));
- }
-
- public void testHkdfInvalidLengths() throws Exception {
- SecretKey inputKey = new SecretKeySpec(HKDF_CASE1_IKM, "AES");
-
- // Negative length
- assertThrows(
- IllegalArgumentException.class,
- () -> CryptoOps.hkdf(inputKey, HKDF_CASE1_SALT, HKDF_CASE1_INFO, -5));
-
- // Too long, would be more than 256 blocks
- assertThrows(
- IllegalArgumentException.class,
- () -> CryptoOps.hkdf(inputKey, HKDF_CASE1_SALT, HKDF_CASE1_INFO, 32 * 256 + 1));
- }
-
- public void testConcat() {
- byte[] a = { 1, 2, 3, 4};
- byte[] b = { 5 , 6 };
- byte[] expectedResult = { 1, 2, 3, 4, 5, 6 };
- byte[] result = CryptoOps.concat(a, b);
- assertEquals(a.length + b.length, result.length);
- assertTrue(Arrays.equals(expectedResult, result));
-
- byte[] empty = { };
- assertEquals(0, CryptoOps.concat(empty, empty).length);
- assertTrue(Arrays.equals(a, CryptoOps.concat(a, empty)));
- assertTrue(Arrays.equals(a, CryptoOps.concat(empty, a)));
-
- assertEquals(0, CryptoOps.concat(null, null).length);
- assertTrue(Arrays.equals(a, CryptoOps.concat(a, null)));
- assertTrue(Arrays.equals(a, CryptoOps.concat(null, a)));
- }
-
- public void testSubarray() {
- byte[] in = { 1, 2, 3, 4, 5, 6, 7 };
- assertTrue(Arrays.equals(in, CryptoOps.subarray(in, 0, in.length)));
- assertEquals(0, CryptoOps.subarray(in, 0, 0).length);
- byte[] expectedResult1 = { 1 };
- assertTrue(Arrays.equals(expectedResult1, CryptoOps.subarray(in, 0, 1)));
- byte[] expectedResult34 = { 3, 4 };
- assertTrue(Arrays.equals(expectedResult34, CryptoOps.subarray(in, 2, 4)));
- assertThrows(IndexOutOfBoundsException.class, () -> CryptoOps.subarray(in, 0, in.length + 1));
- assertThrows(IndexOutOfBoundsException.class, () -> CryptoOps.subarray(in, -1, in.length));
- assertThrows(
- IndexOutOfBoundsException.class, () -> CryptoOps.subarray(in, in.length, in.length));
- assertThrows(
- IndexOutOfBoundsException.class,
- () -> CryptoOps.subarray(in, Integer.MIN_VALUE, in.length));
- assertThrows(
- IndexOutOfBoundsException.class, () -> CryptoOps.subarray(in, 1, Integer.MIN_VALUE));
- }
-}
diff --git a/src/main/javatest/com/google/security/cryptauth/lib/securemessage/NullsGoogle3Test.java b/src/main/javatest/com/google/security/cryptauth/lib/securemessage/NullsGoogle3Test.java
deleted file mode 100644
index c28d2f9..0000000
--- a/src/main/javatest/com/google/security/cryptauth/lib/securemessage/NullsGoogle3Test.java
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2020 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
-//
-// https://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.security.cryptauth.lib.securemessage;
-
-import com.google.common.testing.NullPointerTester;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.SecureMessage;
-import junit.framework.TestCase;
-
-/**
- * Non-portable Google3-based test to check null pointer behavior.
- */
-public class NullsGoogle3Test extends TestCase {
-
- /**
- * We test all of the classes in one place to avoid a proliferation of similar test cases,
- * noting that {@link NullPointerTester} emits the name of the class where the breakge occurs.
- */
- public void testNulls() {
- final NullPointerTester tester = new NullPointerTester();
- tester.testAllPublicStaticMethods(CryptoOps.class);
- tester.testAllPublicStaticMethods(PublicKeyProtoUtil.class);
-
- tester.setDefault(SecureMessage.class, SecureMessage.getDefaultInstance());
- tester.testAllPublicStaticMethods(SecureMessageParser.class);
-
- tester.testAllPublicStaticMethods(SecureMessageBuilder.class);
- tester.testAllPublicConstructors(SecureMessageBuilder.class);
- tester.testAllPublicInstanceMethods(new SecureMessageBuilder());
- }
-}
diff --git a/src/main/javatest/com/google/security/cryptauth/lib/securemessage/PublicKeyProtoUtilTest.java b/src/main/javatest/com/google/security/cryptauth/lib/securemessage/PublicKeyProtoUtilTest.java
deleted file mode 100644
index 8581622..0000000
--- a/src/main/javatest/com/google/security/cryptauth/lib/securemessage/PublicKeyProtoUtilTest.java
+++ /dev/null
@@ -1,412 +0,0 @@
-// Copyright 2020 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
-//
-// https://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.security.cryptauth.lib.securemessage;
-
-import com.google.common.io.BaseEncoding;
-import com.google.protobuf.ByteString;
-import com.google.security.annotations.SuppressInsecureCipherModeCheckerNoReview;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.DhPublicKey;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.EcP256PublicKey;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.GenericPublicKey;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.SimpleRsaPublicKey;
-import java.math.BigInteger;
-import java.security.KeyFactory;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.NoSuchAlgorithmException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.interfaces.ECPublicKey;
-import java.security.spec.ECPoint;
-import java.security.spec.ECPublicKeySpec;
-import java.security.spec.InvalidKeySpecException;
-import java.util.Arrays;
-import javax.crypto.KeyAgreement;
-import javax.crypto.interfaces.DHPrivateKey;
-import javax.crypto.interfaces.DHPublicKey;
-import junit.framework.TestCase;
-
-/** Tests for the PublicKeyProtoUtil class. */
-public class PublicKeyProtoUtilTest extends TestCase {
-
- private static final byte[] ZERO_BYTE = {0};
- private PublicKey ecPublicKey;
- private PublicKey rsaPublicKey;
-
- /**
- * Diffie Hellman {@link PublicKey}s require special treatment, so we store them specifically as a
- * {@link DHPublicKey} to minimize casting.
- */
- private DHPublicKey dhPublicKey;
-
- @Override
- public void setUp() {
- if (!isAndroidOsWithoutEcSupport()) {
- ecPublicKey = PublicKeyProtoUtil.generateEcP256KeyPair().getPublic();
- }
- rsaPublicKey = PublicKeyProtoUtil.generateRSA2048KeyPair().getPublic();
- dhPublicKey = (DHPublicKey) PublicKeyProtoUtil.generateDh2048KeyPair().getPublic();
- }
-
- public void testPublicKeyProtoSpecificEncodeParse() throws Exception {
- if (!isAndroidOsWithoutEcSupport()) {
- assertEquals(
- ecPublicKey,
- PublicKeyProtoUtil.parseEcPublicKey(PublicKeyProtoUtil.encodeEcPublicKey(ecPublicKey)));
- }
-
- assertEquals(
- rsaPublicKey,
- PublicKeyProtoUtil.parseRsa2048PublicKey(
- PublicKeyProtoUtil.encodeRsa2048PublicKey(rsaPublicKey)));
-
- // DHPublicKey objects don't seem to properly implement equals(), so we have to test that
- // the individual y and p values match (it is safe to assume g = 2 is used if p is correct).
- DHPublicKey parsedDHPublicKey =
- PublicKeyProtoUtil.parseDh2048PublicKey(
- PublicKeyProtoUtil.encodeDh2048PublicKey(dhPublicKey));
- assertEquals(dhPublicKey.getY(), parsedDHPublicKey.getY());
- assertEquals(dhPublicKey.getParams().getP(), parsedDHPublicKey.getParams().getP());
- assertEquals(dhPublicKey.getParams().getG(), parsedDHPublicKey.getParams().getG());
- }
-
- public void testPublicKeyProtoGenericEncodeParse() throws Exception {
- if (!isAndroidOsWithoutEcSupport()) {
- assertEquals(
- ecPublicKey,
- PublicKeyProtoUtil.parsePublicKey(
- PublicKeyProtoUtil.encodePaddedEcPublicKey(ecPublicKey)));
- assertEquals(
- ecPublicKey,
- PublicKeyProtoUtil.parsePublicKey(PublicKeyProtoUtil.encodePublicKey(ecPublicKey)));
- }
-
- assertEquals(
- rsaPublicKey,
- PublicKeyProtoUtil.parsePublicKey(PublicKeyProtoUtil.encodePublicKey(rsaPublicKey)));
-
- // See above explanation for why we treat DHPublicKey objects differently.
- DHPublicKey parsedDHPublicKey =
- PublicKeyProtoUtil.parseDh2048PublicKey(
- PublicKeyProtoUtil.encodeDh2048PublicKey(dhPublicKey));
- assertEquals(dhPublicKey.getY(), parsedDHPublicKey.getY());
- assertEquals(dhPublicKey.getParams().getP(), parsedDHPublicKey.getParams().getP());
- assertEquals(dhPublicKey.getParams().getG(), parsedDHPublicKey.getParams().getG());
- }
-
- public void testPaddedECPublicKeyEncodeHasPaddedNullByte() throws Exception {
- if (isAndroidOsWithoutEcSupport()) {
- return;
- }
-
- // Key where the x coordinate is 33 bytes, y coordinate is 32 bytes
- ECPublicKey maxXByteLengthKey =
- buildEcPublicKey(
- BaseEncoding.base64().decode("AM730WQL7ZAmvyAJX4euNdr3+nAIueGlYYGXE6p732h6"),
- BaseEncoding.base64().decode("JEnmaDpKn0fH4/0kKGb97qUSwI2uT+ta0GLe3V7REfk="));
- // Key where both coordinates are 33 bytes
- ECPublicKey maxByteLengthKey =
- buildEcPublicKey(
- BaseEncoding.base64().decode("AOg9TQCxFfVdXv7lO/6UVDyiPsu8XDkEWQIPUfqX6UHP"),
- BaseEncoding.base64().decode("AP/RW8uVyu6QImpbza51CqG1mtBTh5c9pjv9CUwOuB7E"));
- // Key where both coordinates are 32 bytes
- ECPublicKey notMaxByteLengthKey =
- buildEcPublicKey(
- BaseEncoding.base64().decode("M35bxV8HKr0e8v7f4zuXgw6TYFawvikFdI71u9S1ONI="),
- BaseEncoding.base64().decode("OXR+xCpD8AR0VR8TeBXA00eIr3rWE6sV6KrOM6MoWsc="));
- GenericPublicKey encodedMaxXByteLengthKey =
- PublicKeyProtoUtil.encodePublicKey(maxXByteLengthKey);
- GenericPublicKey paddedEncodedMaxXByteLengthKey =
- PublicKeyProtoUtil.encodePaddedEcPublicKey(maxXByteLengthKey);
- GenericPublicKey encodedMaxByteLengthKey = PublicKeyProtoUtil.encodePublicKey(maxByteLengthKey);
- GenericPublicKey paddedEncodedMaxByteLengthKey =
- PublicKeyProtoUtil.encodePaddedEcPublicKey(maxByteLengthKey);
- GenericPublicKey encodedNotMaxByteLengthKey =
- PublicKeyProtoUtil.encodePublicKey(notMaxByteLengthKey);
- GenericPublicKey paddedEncodedNotMaxByteLengthKey =
- PublicKeyProtoUtil.encodePaddedEcPublicKey(notMaxByteLengthKey);
-
- assertEquals(maxXByteLengthKey, PublicKeyProtoUtil.parsePublicKey(encodedMaxXByteLengthKey));
- assertEquals(
- maxXByteLengthKey, PublicKeyProtoUtil.parsePublicKey(paddedEncodedMaxXByteLengthKey));
- assertEquals(maxByteLengthKey, PublicKeyProtoUtil.parsePublicKey(encodedMaxByteLengthKey));
- assertEquals(
- maxByteLengthKey, PublicKeyProtoUtil.parsePublicKey(paddedEncodedMaxByteLengthKey));
- assertEquals(
- notMaxByteLengthKey, PublicKeyProtoUtil.parsePublicKey(paddedEncodedNotMaxByteLengthKey));
- assertEquals(
- notMaxByteLengthKey, PublicKeyProtoUtil.parsePublicKey(encodedNotMaxByteLengthKey));
-
- assertEquals(33, paddedEncodedMaxXByteLengthKey.getEcP256PublicKey().getX().size());
- assertEquals(33, paddedEncodedMaxXByteLengthKey.getEcP256PublicKey().getY().size());
- assertEquals(0, paddedEncodedMaxXByteLengthKey.getEcP256PublicKey().getX().byteAt(0));
- assertEquals(0, paddedEncodedMaxXByteLengthKey.getEcP256PublicKey().getY().byteAt(0));
- assertEquals(33, encodedMaxXByteLengthKey.getEcP256PublicKey().getX().size());
- assertEquals(32, encodedMaxXByteLengthKey.getEcP256PublicKey().getY().size());
-
- assertEquals(33, paddedEncodedMaxByteLengthKey.getEcP256PublicKey().getX().size());
- assertEquals(33, paddedEncodedMaxByteLengthKey.getEcP256PublicKey().getY().size());
- assertEquals(0, paddedEncodedMaxByteLengthKey.getEcP256PublicKey().getX().byteAt(0));
- assertEquals(0, paddedEncodedMaxByteLengthKey.getEcP256PublicKey().getY().byteAt(0));
- assertEquals(33, encodedMaxByteLengthKey.getEcP256PublicKey().getX().size());
- assertEquals(33, encodedMaxByteLengthKey.getEcP256PublicKey().getY().size());
-
- assertEquals(32, encodedNotMaxByteLengthKey.getEcP256PublicKey().getX().size());
- assertEquals(32, encodedNotMaxByteLengthKey.getEcP256PublicKey().getY().size());
- assertEquals(0, paddedEncodedNotMaxByteLengthKey.getEcP256PublicKey().getX().byteAt(0));
- assertEquals(0, paddedEncodedNotMaxByteLengthKey.getEcP256PublicKey().getY().byteAt(0));
- assertEquals(33, paddedEncodedNotMaxByteLengthKey.getEcP256PublicKey().getX().size());
- assertEquals(33, paddedEncodedNotMaxByteLengthKey.getEcP256PublicKey().getY().size());
- }
-
- @SuppressInsecureCipherModeCheckerNoReview
- public void testWrongPublicKeyType() throws Exception {
- KeyPairGenerator dsaGen = KeyPairGenerator.getInstance("DSA");
- dsaGen.initialize(512);
- PublicKey pk = dsaGen.generateKeyPair().getPublic();
-
- if (!isAndroidOsWithoutEcSupport()) {
- // Try to encode it as EC
- try {
- PublicKeyProtoUtil.encodeEcPublicKey(pk);
- fail();
- } catch (IllegalArgumentException expected) {
- }
-
- try {
- PublicKeyProtoUtil.encodePaddedEcPublicKey(pk);
- fail();
- } catch (IllegalArgumentException expected) {
- }
- }
-
- // Try to encode it as RSA
- try {
- PublicKeyProtoUtil.encodeRsa2048PublicKey(pk);
- fail();
- } catch (IllegalArgumentException expected) {
- }
-
- // Try to encode it as DH
- try {
- PublicKeyProtoUtil.encodeDh2048PublicKey(pk);
- fail();
- } catch (IllegalArgumentException expected) {
- }
-
- // Try to encode it as Generic
- try {
- PublicKeyProtoUtil.encodePublicKey(pk);
- fail();
- } catch (IllegalArgumentException expected) {
- }
- }
-
- public void testEcPublicKeyProtoInvalidEncoding() throws Exception {
- if (isAndroidOsWithoutEcSupport()) {
- return;
- }
-
- EcP256PublicKey validProto = PublicKeyProtoUtil.encodeEcPublicKey(ecPublicKey);
- EcP256PublicKey.Builder invalidProto = EcP256PublicKey.newBuilder(validProto);
-
- // Mess up the X coordinate by repeating it twice
- byte[] newX =
- CryptoOps.concat(validProto.getX().toByteArray(), validProto.getX().toByteArray());
- checkParsingFailsFor(invalidProto.setX(ByteString.copyFrom(newX)).build());
-
- // Mess up the Y coordinate by erasing it
- invalidProto = EcP256PublicKey.newBuilder(validProto);
- checkParsingFailsFor(invalidProto.setY(ByteString.EMPTY).build());
-
- // Pick a point that is likely not on the curve by copying X over Y
- invalidProto = EcP256PublicKey.newBuilder(validProto);
- checkParsingFailsFor(invalidProto.setY(validProto.getX()).build());
-
- // Try the point (0, 0)
- invalidProto = EcP256PublicKey.newBuilder(validProto);
- checkParsingFailsFor(
- invalidProto
- .setX(ByteString.copyFrom(ZERO_BYTE))
- .setY(ByteString.copyFrom(ZERO_BYTE))
- .build());
- }
-
- private void checkParsingFailsFor(EcP256PublicKey invalid) {
- try {
- // Should fail to decode
- PublicKeyProtoUtil.parseEcPublicKey(invalid);
- fail();
- } catch (InvalidKeySpecException expected) {
- }
- }
-
- public void testSimpleRsaPublicKeyProtoInvalidEncoding() throws Exception {
- SimpleRsaPublicKey validProto = PublicKeyProtoUtil.encodeRsa2048PublicKey(rsaPublicKey);
- SimpleRsaPublicKey.Builder invalidProto;
-
- // Double the number of bits in the modulus
- invalidProto = SimpleRsaPublicKey.newBuilder(validProto);
- byte[] newN =
- CryptoOps.concat(validProto.getN().toByteArray(), validProto.getN().toByteArray());
- checkParsingFailsFor(invalidProto.setN(ByteString.copyFrom(newN)).build());
-
- // Set the modulus to 0
- invalidProto = SimpleRsaPublicKey.newBuilder(validProto);
- checkParsingFailsFor(invalidProto.setN(ByteString.copyFrom(ZERO_BYTE)).build());
-
- // Set the modulus to 65537 (way too small)
- invalidProto = SimpleRsaPublicKey.newBuilder(validProto);
- checkParsingFailsFor(
- invalidProto.setN(ByteString.copyFrom(BigInteger.valueOf(65537).toByteArray())).build());
- }
-
- private static void checkParsingFailsFor(SimpleRsaPublicKey invalid) {
- try {
- // Should fail to decode
- PublicKeyProtoUtil.parseRsa2048PublicKey(invalid);
- fail();
- } catch (InvalidKeySpecException expected) {
- }
- }
-
- public void testSimpleDhPublicKeyProtoInvalidEncoding() throws Exception {
- DhPublicKey validProto = PublicKeyProtoUtil.encodeDh2048PublicKey(dhPublicKey);
- DhPublicKey.Builder invalidProto;
-
- // Double the number of bits in the public element encoding
- invalidProto = DhPublicKey.newBuilder(validProto);
- byte[] newY =
- CryptoOps.concat(validProto.getY().toByteArray(), validProto.getY().toByteArray());
- checkParsingFailsFor(invalidProto.setY(ByteString.copyFrom(newY)).build());
-
- // Set the public element to 0
- invalidProto = DhPublicKey.newBuilder(validProto);
- checkParsingFailsFor(invalidProto.setY(ByteString.copyFrom(ZERO_BYTE)).build());
- }
-
- private static void checkParsingFailsFor(DhPublicKey invalid) {
- try {
- // Should fail to decode
- PublicKeyProtoUtil.parseDh2048PublicKey(invalid);
- fail();
- } catch (InvalidKeySpecException expected) {
- }
- }
-
- public void testDhKeyAgreementWorks() throws Exception {
- int minExpectedSecretLength = (PublicKeyProtoUtil.DH_P.bitLength() / 8) - 4;
-
- KeyPair clientKeyPair = PublicKeyProtoUtil.generateDh2048KeyPair();
- KeyPair serverKeyPair = PublicKeyProtoUtil.generateDh2048KeyPair();
- BigInteger clientY = ((DHPublicKey) clientKeyPair.getPublic()).getY();
- BigInteger serverY = ((DHPublicKey) serverKeyPair.getPublic()).getY();
- assertFalse(clientY.equals(serverY)); // DHPublicKeys should not be equal
-
- // Run client side of the key exchange
- byte[] clientSecret = doDhAgreement(clientKeyPair.getPrivate(), serverKeyPair.getPublic());
- assert (clientSecret.length >= minExpectedSecretLength);
-
- // Run the server side of the key exchange
- byte[] serverSecret = doDhAgreement(serverKeyPair.getPrivate(), clientKeyPair.getPublic());
- assert (serverSecret.length >= minExpectedSecretLength);
-
- assertTrue(Arrays.equals(clientSecret, serverSecret));
- }
-
- public void testDh2048PrivateKeyEncoding() throws Exception {
- KeyPair testPair = PublicKeyProtoUtil.generateDh2048KeyPair();
- DHPrivateKey sk = (DHPrivateKey) testPair.getPrivate();
- DHPrivateKey skParsed =
- PublicKeyProtoUtil.parseDh2048PrivateKey(PublicKeyProtoUtil.encodeDh2048PrivateKey(sk));
- assertEquals(sk.getX(), skParsed.getX());
- assertEquals(sk.getParams().getP(), skParsed.getParams().getP());
- assertEquals(sk.getParams().getG(), skParsed.getParams().getG());
- }
-
- public void testParseEcPublicKeyOnLegacyPlatform() {
- if (!PublicKeyProtoUtil.isLegacyCryptoRequired()) {
- return; // This test only runs on legacy platforms
- }
- byte[] pointBytes = {
- 1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
- 1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
- 1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
- 1, 2
- };
-
- try {
- PublicKeyProtoUtil.parseEcPublicKey(
- EcP256PublicKey.newBuilder()
- .setX(ByteString.copyFrom(pointBytes))
- .setY(ByteString.copyFrom(pointBytes))
- .build());
- fail();
- } catch (InvalidKeySpecException expected) {
- // Should get this specific exception when EC doesn't work
- }
- }
-
- public void testIsLegacyCryptoRequired() {
- assertEquals(isAndroidOsWithoutEcSupport(), PublicKeyProtoUtil.isLegacyCryptoRequired());
- }
-
- /** @return true if running on an Android OS that doesn't support Elliptic Curve algorithms */
- public static boolean isAndroidOsWithoutEcSupport() {
- try {
- Class<?> clazz = ClassLoader.getSystemClassLoader().loadClass("android.os.Build$VERSION");
- int sdkVersion = clazz.getField("SDK_INT").getInt(null);
- if (sdkVersion < PublicKeyProtoUtil.ANDROID_HONEYCOMB_SDK_INT) {
- return true;
- }
- } catch (ClassNotFoundException e) {
- // Not running on Android
- return false;
- } catch (SecurityException e) {
- throw new AssertionError(e);
- } catch (NoSuchFieldException e) {
- throw new AssertionError(e);
- } catch (IllegalArgumentException e) {
- throw new AssertionError(e);
- } catch (IllegalAccessException e) {
- throw new AssertionError(e);
- }
- return false;
- }
-
- @SuppressInsecureCipherModeCheckerNoReview
- private static byte[] doDhAgreement(PrivateKey secretKey, PublicKey peerKey) throws Exception {
- KeyAgreement agreement = KeyAgreement.getInstance("DH");
- agreement.init(secretKey);
- agreement.doPhase(peerKey, true);
- return agreement.generateSecret();
- }
-
- private static ECPublicKey buildEcPublicKey(byte[] encodedX, byte[] encodedY) throws Exception {
- try {
- BigInteger wX = new BigInteger(encodedX);
- BigInteger wY = new BigInteger(encodedY);
- return (ECPublicKey)
- KeyFactory.getInstance("EC")
- .generatePublic(
- new ECPublicKeySpec(
- new ECPoint(wX, wY),
- ((ECPublicKey) PublicKeyProtoUtil.generateEcP256KeyPair().getPublic())
- .getParams()));
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException(e);
- }
- }
-}
diff --git a/src/main/javatest/com/google/security/cryptauth/lib/securemessage/SecureMessageSimpleTestVectorTest.java b/src/main/javatest/com/google/security/cryptauth/lib/securemessage/SecureMessageSimpleTestVectorTest.java
deleted file mode 100644
index 285b259..0000000
--- a/src/main/javatest/com/google/security/cryptauth/lib/securemessage/SecureMessageSimpleTestVectorTest.java
+++ /dev/null
@@ -1,403 +0,0 @@
-// Copyright 2020 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
-//
-// https://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.security.cryptauth.lib.securemessage;
-
-import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.security.cryptauth.lib.securemessage.CryptoOps.EncType;
-import com.google.security.cryptauth.lib.securemessage.CryptoOps.SigType;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.GenericPublicKey;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.Header;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.HeaderAndBody;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.SecureMessage;
-import java.security.KeyFactory;
-import java.security.KeyPair;
-import java.security.NoSuchAlgorithmException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.spec.InvalidKeySpecException;
-import java.security.spec.PKCS8EncodedKeySpec;
-import java.util.Arrays;
-import javax.crypto.KeyGenerator;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.SecretKeySpec;
-import junit.framework.TestCase;
-
-/**
- * Tests the library against some very basic test vectors, to help ensure wire-format
- * compatibility is not broken.
- */
-public class SecureMessageSimpleTestVectorTest extends TestCase {
-
- private static final KeyFactory EC_KEY_FACTORY;
- static {
- try {
- if (PublicKeyProtoUtil.isLegacyCryptoRequired()) {
- EC_KEY_FACTORY = null;
- } else {
- EC_KEY_FACTORY = KeyFactory.getInstance("EC");
- }
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
- private static final byte[] TEST_ASSOCIATED_DATA = {
- 11, 22, 33, 44, 55
- };
- private static final byte[] TEST_METADATA = {
- 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28
- };
- private static final byte[] TEST_VKID = {
- 0, 0, 1
- };
- private static final byte[] TEST_DKID = {
- -1, -1, 0,
- };
- private static final byte[] TEST_MESSAGE = {
- 0, 99, 1, 98, 2, 97, 3, 96, 4, 95, 5, 94, 6, 93, 7, 92, 8, 91, 9, 90
- };
-
- // The following fields are initialized below, in a static block that contains auto-generated test
- // vectors. Initialization can't just be done inline due to code that throws checked exceptions.
- private static final PublicKey TEST_EC_PUBLIC_KEY;
- private static final PrivateKey TEST_EC_PRIVATE_KEY;
- private static final SecretKey TEST_KEY1;
- private static final SecretKey TEST_KEY2;
- private static final byte[] TEST_VECTOR_ECDSA_ONLY;
- private static final byte[] TEST_VECTOR_ECDSA_AND_AES;
- private static final byte[] TEST_VECTOR_HMAC_AND_AES_SAME_KEYS;
- private static final byte[] TEST_VECTOR_HMAC_AND_AES_DIFFERENT_KEYS;
-
- public void testEcdsaOnly() throws Exception {
- if (PublicKeyProtoUtil.isLegacyCryptoRequired()) {
- // On older Android platforms we can't run this test.
- return;
- }
- SecureMessage testVector = SecureMessage.parseFrom(TEST_VECTOR_ECDSA_ONLY);
- Header unverifiedHeader = SecureMessageParser.getUnverifiedHeader(testVector);
- HeaderAndBody headerAndBody = SecureMessageParser.parseSignedCleartextMessage(
- testVector, TEST_EC_PUBLIC_KEY, SigType.ECDSA_P256_SHA256, TEST_ASSOCIATED_DATA);
- assertTrue(Arrays.equals(
- unverifiedHeader.toByteArray(),
- headerAndBody.getHeader().toByteArray()));
- assertTrue(Arrays.equals(TEST_MESSAGE, headerAndBody.getBody().toByteArray()));
- assertEquals(TEST_ASSOCIATED_DATA.length, unverifiedHeader.getAssociatedDataLength());
- assertTrue(Arrays.equals(TEST_METADATA, unverifiedHeader.getPublicMetadata().toByteArray()));
- assertTrue(Arrays.equals(TEST_VKID, unverifiedHeader.getVerificationKeyId().toByteArray()));
- assertFalse(unverifiedHeader.hasDecryptionKeyId());
- }
-
- public void testEcdsaAndAes() throws Exception {
- if (PublicKeyProtoUtil.isLegacyCryptoRequired()) {
- // On older Android platforms we can't run this test.
- return;
- }
- SecureMessage testVector = SecureMessage.parseFrom(TEST_VECTOR_ECDSA_AND_AES);
- Header unverifiedHeader = SecureMessageParser.getUnverifiedHeader(testVector);
- HeaderAndBody headerAndBody = SecureMessageParser.parseSignCryptedMessage(
- testVector,
- TEST_EC_PUBLIC_KEY,
- SigType.ECDSA_P256_SHA256,
- TEST_KEY1,
- EncType.AES_256_CBC,
- TEST_ASSOCIATED_DATA);
- assertTrue(Arrays.equals(
- unverifiedHeader.toByteArray(),
- headerAndBody.getHeader().toByteArray()));
- assertTrue(Arrays.equals(TEST_MESSAGE, headerAndBody.getBody().toByteArray()));
- assertEquals(TEST_ASSOCIATED_DATA.length, unverifiedHeader.getAssociatedDataLength());
- assertTrue(Arrays.equals(TEST_METADATA, unverifiedHeader.getPublicMetadata().toByteArray()));
- assertTrue(Arrays.equals(TEST_VKID, unverifiedHeader.getVerificationKeyId().toByteArray()));
- assertTrue(Arrays.equals(TEST_DKID, unverifiedHeader.getDecryptionKeyId().toByteArray()));
- }
-
- public void testHmacAndAesSameKeys() throws Exception {
- SecureMessage testVector = SecureMessage.parseFrom(TEST_VECTOR_HMAC_AND_AES_SAME_KEYS);
- Header unverifiedHeader = SecureMessageParser.getUnverifiedHeader(testVector);
-
- HeaderAndBody headerAndBody = SecureMessageParser.parseSignCryptedMessage(
- testVector,
- TEST_KEY1,
- SigType.HMAC_SHA256,
- TEST_KEY1,
- EncType.AES_256_CBC,
- TEST_ASSOCIATED_DATA);
- assertTrue(Arrays.equals(
- unverifiedHeader.toByteArray(),
- headerAndBody.getHeader().toByteArray()));
- assertTrue(Arrays.equals(TEST_MESSAGE, headerAndBody.getBody().toByteArray()));
- assertEquals(TEST_ASSOCIATED_DATA.length, unverifiedHeader.getAssociatedDataLength());
- assertTrue(Arrays.equals(TEST_METADATA, unverifiedHeader.getPublicMetadata().toByteArray()));
- assertTrue(Arrays.equals(TEST_VKID, unverifiedHeader.getVerificationKeyId().toByteArray()));
- assertTrue(Arrays.equals(TEST_DKID, unverifiedHeader.getDecryptionKeyId().toByteArray()));
- }
-
- public void testHmacAndAesDifferentKeys() throws Exception {
- SecureMessage testVector = SecureMessage.parseFrom(TEST_VECTOR_HMAC_AND_AES_DIFFERENT_KEYS);
- Header unverifiedHeader = SecureMessageParser.getUnverifiedHeader(testVector);
- HeaderAndBody headerAndBody = SecureMessageParser.parseSignCryptedMessage(
- testVector,
- TEST_KEY1,
- SigType.HMAC_SHA256,
- TEST_KEY2,
- EncType.AES_256_CBC,
- TEST_ASSOCIATED_DATA);
- assertTrue(Arrays.equals(
- unverifiedHeader.toByteArray(),
- headerAndBody.getHeader().toByteArray()));
- assertTrue(Arrays.equals(TEST_MESSAGE, headerAndBody.getBody().toByteArray()));
- assertEquals(TEST_ASSOCIATED_DATA.length, unverifiedHeader.getAssociatedDataLength());
- assertTrue(Arrays.equals(TEST_METADATA, unverifiedHeader.getPublicMetadata().toByteArray()));
- assertTrue(Arrays.equals(TEST_VKID, unverifiedHeader.getVerificationKeyId().toByteArray()));
- assertTrue(Arrays.equals(TEST_DKID, unverifiedHeader.getDecryptionKeyId().toByteArray()));
- }
-
- /**
- * This code emits the test vectors to {@code System.out}. It will not generate fresh test
- * vectors unless an existing test vector is set to {@code null}, but it contains all of the code
- * used to the generate the test vector values. Ideally, existing test vectors should never be
- * regenerated, but having this code available should make it easier to add new test vectors.
- */
- public void testGenerateTestVectorsPseudoTest() throws Exception {
- if (PublicKeyProtoUtil.isLegacyCryptoRequired()) {
- // On older Android platforms we can't run this test.
- return;
- }
- System.out.printf(" static {\n try {\n");
- String indent = " ";
- PublicKey testEcPublicKey = TEST_EC_PUBLIC_KEY;
- PrivateKey testEcPrivateKey = TEST_EC_PRIVATE_KEY;
- if (testEcPublicKey == null) {
- KeyPair testEcKeyPair = PublicKeyProtoUtil.generateEcP256KeyPair();
- testEcPublicKey = testEcKeyPair.getPublic();
- testEcPrivateKey = testEcKeyPair.getPrivate();
- }
- System.out.printf("%s%s = parsePublicKey(new byte[] %s);\n",
- indent,
- "TEST_EC_PUBLIC_KEY",
- byteArrayToJavaCode(indent, encodePublicKey(testEcPublicKey)));
- System.out.printf("%s%s = parseEcPrivateKey(new byte[] %s);\n",
- indent,
- "TEST_EC_PRIVATE_KEY",
- byteArrayToJavaCode(indent, encodeEcPrivateKey(testEcPrivateKey)));
-
- SecretKey testKey1 = TEST_KEY1;
- if (testKey1 == null) {
- testKey1 = makeAesKey();
- }
- System.out.printf("%s%s = new SecretKeySpec(new byte[] %s, \"AES\");\n",
- indent,
- "TEST_KEY1",
- byteArrayToJavaCode(indent, testKey1.getEncoded()));
-
- SecretKey testKey2 = TEST_KEY2;
- if (testKey2 == null) {
- testKey2 = makeAesKey();
- }
- System.out.printf("%s%s = new SecretKeySpec(new byte[] %s, \"AES\");\n",
- indent,
- "TEST_KEY2",
- byteArrayToJavaCode(indent, testKey2.getEncoded()));
-
- byte[] testVectorEcdsaOnly = TEST_VECTOR_ECDSA_ONLY;
- if (testVectorEcdsaOnly == null) {
- testVectorEcdsaOnly = new SecureMessageBuilder()
- .setAssociatedData(TEST_ASSOCIATED_DATA)
- .setPublicMetadata(TEST_METADATA)
- .setVerificationKeyId(TEST_VKID)
- .buildSignedCleartextMessage(
- testEcPrivateKey, SigType.ECDSA_P256_SHA256, TEST_MESSAGE).toByteArray();
- }
- printInitializerFor(indent, "TEST_VECTOR_ECDSA_ONLY", testVectorEcdsaOnly);
-
- byte[] testVectorEcdsaAndAes = TEST_VECTOR_ECDSA_AND_AES;
- if (testVectorEcdsaAndAes == null) {
- testVectorEcdsaAndAes = new SecureMessageBuilder()
- .setAssociatedData(TEST_ASSOCIATED_DATA)
- .setDecryptionKeyId(TEST_DKID)
- .setPublicMetadata(TEST_METADATA)
- .setVerificationKeyId(TEST_VKID)
- .buildSignCryptedMessage(
- testEcPrivateKey,
- SigType.ECDSA_P256_SHA256,
- testKey1,
- EncType.AES_256_CBC,
- TEST_MESSAGE).toByteArray();
- }
- printInitializerFor(indent, "TEST_VECTOR_ECDSA_AND_AES", testVectorEcdsaAndAes);
-
- byte[] testVectorHmacAndAesSameKeys = TEST_VECTOR_HMAC_AND_AES_SAME_KEYS;
- if (testVectorHmacAndAesSameKeys == null) {
- testVectorHmacAndAesSameKeys = new SecureMessageBuilder()
- .setAssociatedData(TEST_ASSOCIATED_DATA)
- .setDecryptionKeyId(TEST_DKID)
- .setPublicMetadata(TEST_METADATA)
- .setVerificationKeyId(TEST_VKID)
- .buildSignCryptedMessage(
- testKey1,
- SigType.HMAC_SHA256,
- testKey1,
- EncType.AES_256_CBC,
- TEST_MESSAGE).toByteArray();
- }
- printInitializerFor(indent, "TEST_VECTOR_HMAC_AND_AES_SAME_KEYS", testVectorHmacAndAesSameKeys);
-
- byte[] testVectorHmacAndAesDifferentKeys = TEST_VECTOR_HMAC_AND_AES_DIFFERENT_KEYS;
- if (testVectorHmacAndAesDifferentKeys == null) {
- testVectorHmacAndAesDifferentKeys = new SecureMessageBuilder()
- .setAssociatedData(TEST_ASSOCIATED_DATA)
- .setDecryptionKeyId(TEST_DKID)
- .setPublicMetadata(TEST_METADATA)
- .setVerificationKeyId(TEST_VKID)
- .buildSignCryptedMessage(
- testKey1,
- SigType.HMAC_SHA256,
- testKey2,
- EncType.AES_256_CBC,
- TEST_MESSAGE).toByteArray();
- }
- printInitializerFor(
- indent, "TEST_VECTOR_HMAC_AND_AES_DIFFERENT_KEYS", testVectorHmacAndAesDifferentKeys);
-
- System.out.printf(
- " } catch (Exception e) {\n throw new RuntimeException(e);\n }\n }\n");
- }
-
- private SecretKey makeAesKey() throws NoSuchAlgorithmException {
- KeyGenerator aesKeygen = KeyGenerator.getInstance("AES");
- aesKeygen.init(256);
- return aesKeygen.generateKey();
- }
-
- private void printInitializerFor(String indent, String name, byte[] value) {
- System.out.printf("%s%s = new byte[] %s;\n",
- indent,
- name,
- byteArrayToJavaCode(indent, value));
- }
-
- private static String byteArrayToJavaCode(String lineIndent, byte[] array) {
- String newline = "\n" + lineIndent + " ";
- String unwrappedArray = Arrays.toString(array).replace("[", "").replace("]", "");
- int wrapAfter = 16;
- int count = wrapAfter;
- StringBuilder result = new StringBuilder("{");
- for (String entry : unwrappedArray.split(" ")) {
- if (++count > wrapAfter) {
- result.append(newline);
- count = 0;
- } else {
- result.append(" ");
- }
- result.append(entry);
- }
- result.append(" }");
- return result.toString();
- }
-
- private static byte[] encodePublicKey(PublicKey pk) {
- return PublicKeyProtoUtil.encodePublicKey(pk).toByteArray();
- }
-
- private static PublicKey parsePublicKey(byte[] encodedPk)
- throws InvalidKeySpecException, InvalidProtocolBufferException {
- GenericPublicKey gpk = GenericPublicKey.parseFrom(encodedPk);
- if (PublicKeyProtoUtil.isLegacyCryptoRequired()
- && gpk.getType() == SecureMessageProto.PublicKeyType.EC_P256) {
- return null;
- }
- return PublicKeyProtoUtil.parsePublicKey(gpk);
- }
-
- private static byte[] encodeEcPrivateKey(PrivateKey sk) {
- return sk.getEncoded();
- }
-
- private static PrivateKey parseEcPrivateKey(byte[] sk) throws InvalidKeySpecException {
- if (PublicKeyProtoUtil.isLegacyCryptoRequired()) {
- return null;
- }
- return EC_KEY_FACTORY.generatePrivate(new PKCS8EncodedKeySpec(sk));
- }
-
- // The following block of code was automatically generated by cut and pasting the output of the
- // generateTestVectorsPseudoTest, which should reliably emit this same test vectors. Please
- // DO NOT DELETE any of these existing test vectors unless you _really_ know what you are doing.
- //
- // --- AUTO GENERATED CODE BEGINS HERE ---
- static {
- try {
- TEST_EC_PUBLIC_KEY = parsePublicKey(new byte[] {
- 8, 1, 18, 70, 10, 33, 0, -109, 9, 5, 8, -89, -3, -68, -86, -19, 17,
- -126, -11, -95, 35, 101, 102, -57, -84, -118, 73, 83, 66, -62, -49, -91, 71, -19,
- 52, 123, 113, 119, 45, 18, 33, 0, -65, -19, 83, -66, -12, 62, 102, -67, 116,
- 64, 42, 55, -84, -101, 90, -106, 113, -89, -30, 57, -112, 96, -99, -126, 14, 83,
- 41, 95, -24, -114, 23, -5 });
- TEST_EC_PRIVATE_KEY = parseEcPrivateKey(new byte[] {
- 48, 65, 2, 1, 0, 48, 19, 6, 7, 42, -122, 72, -50, 61, 2, 1, 6,
- 8, 42, -122, 72, -50, 61, 3, 1, 7, 4, 39, 48, 37, 2, 1, 1, 4,
- 32, 26, -82, -61, -86, -59, -8, 2, -62, -17, -20, 122, 3, 85, -102, -76, 81,
- 51, 39, -9, 12, 99, -117, 127, 19, 121, 109, -31, -49, 110, 121, 76, -107 });
- TEST_KEY1 = new SecretKeySpec(new byte[] {
- -89, 105, 62, -41, -75, 78, 70, 110, -62, -58, -80, -81, -99, -62, 39, 38, 37,
- -7, -112, -83, 81, 23, 125, -72, -100, 103, -34, -23, -68, 21, -46, -104 }, "AES");
- TEST_KEY2 = new SecretKeySpec(new byte[] {
- -6, 48, 107, 61, -99, -89, 111, 33, 70, 54, -13, 111, 81, -120, 50, 89, -119,
- -113, -114, 63, 12, -68, 40, 42, -77, -58, -49, 18, 69, 91, -20, -65 }, "AES");
- TEST_VECTOR_ECDSA_ONLY = new byte[] {
- 10, 56, 10, 32, 8, 2, 16, 1, 26, 3, 0, 0, 1, 50, 19, 10, 11,
- 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28,
- 56, 5, 18, 20, 0, 99, 1, 98, 2, 97, 3, 96, 4, 95, 5, 94, 6,
- 93, 7, 92, 8, 91, 9, 90, 18, 72, 48, 70, 2, 33, 0, -79, 59, 50,
- 21, 54, 61, -92, 77, -34, -77, -45, -105, 107, -28, -19, 91, -78, 120, 68, 33,
- 11, -76, -1, 50, 64, -127, -78, 6, 108, 115, -13, 126, 2, 33, 0, -72, -44,
- 52, 93, 105, 109, -127, -111, 11, 33, -111, 97, -114, 9, 117, -68, -45, 64, 63,
- 43, 60, -44, -89, -107, -59, -45, 56, 100, -66, -40, 46, -60 };
- TEST_VECTOR_ECDSA_AND_AES = new byte[] {
- 10, 107, 10, 55, 8, 2, 16, 2, 26, 3, 0, 0, 1, 34, 3, -1, -1,
- 0, 42, 16, -86, 16, 55, -8, -85, -47, -77, -36, -127, 44, -10, -44, -63, 115,
- -111, 26, 50, 19, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
- 23, 24, 25, 26, 27, 28, 56, 5, 18, 48, -110, 23, -67, 122, -118, 96, -4,
- 32, -113, -104, -107, -16, 76, 37, -61, -67, -63, 90, 38, 96, -47, -105, 56, -34,
- 50, -30, 82, 25, 100, 36, 69, 50, 68, 60, 38, 96, -108, -49, -73, -10, -62,
- -76, -45, -105, -86, 93, 28, 34, 18, 70, 48, 68, 2, 33, 0, -87, -103, 11,
- -70, 34, 33, -41, 90, -83, -74, 19, -13, 127, -43, -116, -32, 88, -13, 125, -122,
- 56, -21, 79, 47, 101, 89, -80, -43, 102, 92, 4, -15, 2, 31, 109, -69, 35,
- 21, 44, -27, -77, 32, 17, -90, -68, 113, 55, -24, -122, 40, 81, 51, 0, -84,
- -29, -12, -26, 73, 105, -32, 116, -28, 84, -116, -117 };
- TEST_VECTOR_HMAC_AND_AES_SAME_KEYS = new byte[] {
- 10, 91, 10, 55, 8, 1, 16, 2, 26, 3, 0, 0, 1, 34, 3, -1, -1,
- 0, 42, 16, -110, 48, 67, 67, -31, 24, -42, 13, -44, -109, 6, 113, 34, -70,
- 121, 6, 50, 19, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
- 23, 24, 25, 26, 27, 28, 56, 5, 18, 32, -44, -102, -16, 123, 113, -75, 88,
- -33, 118, 25, 60, -65, 109, 26, -70, -123, 58, -114, 126, 8, 106, -28, 65, -38,
- -4, 68, -78, -91, 49, -13, 22, -122, 18, 32, 20, -120, -113, -76, 85, -35, -53,
- 37, -18, 66, -38, 32, 10, 30, 89, 112, -39, -27, 24, 93, -36, -100, -127, -79,
- 94, -7, -19, -41, -47, -29, 1, 12 };
- TEST_VECTOR_HMAC_AND_AES_DIFFERENT_KEYS = new byte[] {
- 10, 107, 10, 55, 8, 1, 16, 2, 26, 3, 0, 0, 1, 34, 3, -1, -1,
- 0, 42, 16, -96, -7, 39, 79, -37, 40, 1, -30, 97, 0, 123, -7, -124, -75,
- -127, -18, 50, 19, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
- 23, 24, 25, 26, 27, 28, 56, 5, 18, 48, 90, 40, -48, -113, 84, -32, 47,
- 98, 54, -128, 127, 115, 32, 87, -86, 4, -26, 99, 9, -88, 13, 77, 127, 114,
- -48, -117, -94, 96, -86, -105, -123, 11, 116, -69, -83, -110, 3, -10, 0, -34, 72,
- 10, -58, 3, -119, -94, 23, -114, 18, 32, -25, -126, 95, 125, -110, -62, -36, -78,
- 97, 72, -54, -114, 97, -68, -46, 107, 53, 55, -57, 88, 127, -20, -23, 80, -9,
- -91, 115, 42, 24, 49, -76, -111 };
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-}
diff --git a/src/main/javatest/com/google/security/cryptauth/lib/securemessage/SecureMessageTest.java b/src/main/javatest/com/google/security/cryptauth/lib/securemessage/SecureMessageTest.java
deleted file mode 100644
index 40e5091..0000000
--- a/src/main/javatest/com/google/security/cryptauth/lib/securemessage/SecureMessageTest.java
+++ /dev/null
@@ -1,766 +0,0 @@
-// Copyright 2020 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
-//
-// https://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.security.cryptauth.lib.securemessage;
-
-import com.google.protobuf.ByteString;
-import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.protobuf.UninitializedMessageException;
-import com.google.security.cryptauth.lib.securemessage.CryptoOps.EncType;
-import com.google.security.cryptauth.lib.securemessage.CryptoOps.SigType;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.EcP256PublicKey;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.GenericPublicKey;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.Header;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.HeaderAndBody;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.SecureMessage;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.SimpleRsaPublicKey;
-import java.security.InvalidKeyException;
-import java.security.Key;
-import java.security.KeyPair;
-import java.security.NoSuchAlgorithmException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.SecureRandom;
-import java.security.SignatureException;
-import java.security.spec.InvalidKeySpecException;
-import java.util.Arrays;
-import java.util.List;
-import javax.crypto.KeyGenerator;
-import javax.crypto.SecretKey;
-import junit.framework.TestCase;
-
-/**
- * Tests for the SecureMessageBuilder and SecureMessageParser classes.
- */
-public class SecureMessageTest extends TestCase {
- // Not to be used when generating cross-platform test vectors (due to default charset encoding)
- public static final byte[] TEST_MESSAGE =
- "Testing 1 2 3... Testing 1 2 3... Testing 1 2 3...".getBytes();
-
- private static final byte[] TEST_KEY_ID =
- { 0, 1, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };
- // Not to be used when generating cross-platform test vectors (due to default charset encoding)
- private static final byte[] TEST_METADATA = "Some protocol metadata string goes here".getBytes();
- private static final byte[] TEST_ASSOCIATED_DATA = {
- 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0, 9, 0, 10, 0, 11 };
- private static final byte[] ZERO_BYTE = { 0 };
- private static final byte[] EMPTY_BYTES = { };
-
- private static final List<byte[]> MESSAGE_VALUES = Arrays.asList(
- EMPTY_BYTES,
- TEST_MESSAGE
- );
-
- private static final List<byte[]> KEY_ID_VALUES = Arrays.asList(
- null,
- EMPTY_BYTES,
- TEST_KEY_ID);
-
- private static final List<byte[]> METADATA_VALUES = Arrays.asList(
- null,
- EMPTY_BYTES,
- TEST_METADATA
- );
-
- private static final List<byte[]> ASSOCIATED_DATA_VALUES = Arrays.asList(
- null,
- ZERO_BYTE,
- TEST_ASSOCIATED_DATA);
-
- private byte[] message;
- private byte[] metadata;
- private byte[] verificationKeyId;
- private byte[] decryptionKeyId;
- private byte[] associatedData;
- private PublicKey ecPublicKey;
- private PrivateKey ecPrivateKey;
- private PublicKey rsaPublicKey;
- private PrivateKey rsaPrivateKey;
- private SecretKey aesEncryptionKey;
- private SecretKey hmacKey;
- private SecureMessageBuilder secureMessageBuilder = new SecureMessageBuilder();
- private SecureRandom rng = new SecureRandom();
-
- @Override
- public void setUp() {
- message = TEST_MESSAGE;
- metadata = null;
- verificationKeyId = null;
- decryptionKeyId = null;
- associatedData = null;
- if (!PublicKeyProtoUtil.isLegacyCryptoRequired()) {
- KeyPair ecKeyPair = PublicKeyProtoUtil.generateEcP256KeyPair();
- ecPublicKey = ecKeyPair.getPublic();
- ecPrivateKey = ecKeyPair.getPrivate();
- }
- KeyPair rsaKeyPair = PublicKeyProtoUtil.generateRSA2048KeyPair();
- rsaPublicKey = rsaKeyPair.getPublic();
- rsaPrivateKey = rsaKeyPair.getPrivate();
- try {
- aesEncryptionKey = makeAesKey();
- hmacKey = makeAesKey();
- } catch (NoSuchAlgorithmException e) {
- e.printStackTrace();
- fail();
- }
- secureMessageBuilder.reset();
- }
-
- private SecureMessage sign(SigType sigType) throws NoSuchAlgorithmException, InvalidKeyException {
- return getPreconfiguredBuilder().buildSignedCleartextMessage(
- getSigningKeyFor(sigType), sigType, message);
- }
-
- private SecureMessage signCrypt(SigType sigType, EncType encType)
- throws NoSuchAlgorithmException, InvalidKeyException {
- return getPreconfiguredBuilder().buildSignCryptedMessage(
- getSigningKeyFor(sigType), sigType, aesEncryptionKey, encType, message);
- }
-
- private void verify(SecureMessage signed, SigType sigType)
- throws NoSuchAlgorithmException, InvalidKeyException, SignatureException,
- InvalidProtocolBufferException {
- HeaderAndBody headerAndBody = SecureMessageParser.parseSignedCleartextMessage(
- signed,
- getVerificationKeyFor(sigType),
- sigType,
- associatedData);
- consistencyCheck(signed, headerAndBody, sigType, EncType.NONE);
- }
-
- private void verifyDecrypt(SecureMessage encryptedAndSigned, SigType sigType, EncType encType)
- throws InvalidProtocolBufferException, InvalidKeyException, NoSuchAlgorithmException,
- SignatureException {
- HeaderAndBody headerAndBody = SecureMessageParser.parseSignCryptedMessage(
- encryptedAndSigned,
- getVerificationKeyFor(sigType),
- sigType,
- aesEncryptionKey,
- encType,
- associatedData);
- consistencyCheck(encryptedAndSigned, headerAndBody, sigType, encType);
- }
-
- // A collection of different kinds of "alterations" that can be made to SecureMessage protos.
- enum Alteration {
- DECRYPTION_KEY_ID,
- ENCTYPE,
- HEADER_AND_BODY_PROTO,
- MESSAGE,
- METADATA,
- RESIGNCRYPTION_ATTACK,
- SIGTYPE,
- VERIFICATION_KEY_ID,
- ASSOCIATED_DATA_LENGTH,
- }
-
- private void doSignAndVerify(SigType sigType) throws Exception {
- System.out.println("BEGIN_TEST -- Testing SigType: " + sigType + " with:");
- System.out.println("VerificationKeyId: " + Arrays.toString(verificationKeyId));
- System.out.println("Metadata: " + Arrays.toString(metadata));
- System.out.println("AssociatedData: " + Arrays.toString(associatedData));
- System.out.println("Message: " + Arrays.toString(message));
- // Positive test cases
- SecureMessage signed = sign(sigType);
- verify(signed, sigType);
-
- // Negative test cases
- for (Alteration altType : getAlterationsToTest()) {
- System.out.println("Testing alteration: " + altType.toString());
- SecureMessage modified = modifyMessage(signed, altType);
- try {
- verify(modified, sigType);
- fail(altType.toString());
- } catch (SignatureException e) {
- // We expect this
- }
- }
-
- // Try verifying with the wrong associated data
- if ((associatedData == null) || (associatedData.length == 0)) {
- associatedData = ZERO_BYTE;
- } else {
- associatedData = null;
- }
- try {
- verify(signed, sigType);
- fail("Expected verification to fail due to incorrect associatedData");
- } catch (SignatureException e) {
- // We expect this
- }
-
- System.out.println("PASS_TEST -- Testing SigType: " + sigType);
- }
-
- private List<Alteration> getAlterationsToTest() {
- if (isRunningInAndroid()) {
- // Android is very slow. Only try one alteration attack, intead of all of them.
- int randomAlteration = Math.abs(rng.nextInt()) % Alteration.values().length;
- return Arrays.asList(Alteration.values()[randomAlteration]);
- } else {
- // Just try all of them
- return Arrays.asList(Alteration.values());
- }
- }
-
- private void doSignCryptAndVerifyDecrypt(SigType sigType) throws Exception {
- // For now, EncType is always AES_256_CBC
- EncType encType = EncType.AES_256_CBC;
- System.out.println("BEGIN_TEST -- Testing SigType: " + sigType
- + " EncType: " + encType + " with:");
- System.out.println("DecryptionKeyId: " + Arrays.toString(decryptionKeyId));
- System.out.println("VerificationKeyId: " + Arrays.toString(verificationKeyId));
- System.out.println("Metadata: " + Arrays.toString(metadata));
- System.out.println("AssociatedData: " + Arrays.toString(associatedData));
- System.out.println("Message: " + Arrays.toString(message));
- SecureMessage encryptedAndSigned = null;
- encryptedAndSigned = signCrypt(sigType, encType);
- verifyDecrypt(encryptedAndSigned, sigType, encType);
-
- // Negative test cases
- for (Alteration altType : getAlterationsToTest()) {
- if (skipAlterationTestFor(altType, sigType)) {
- System.out.println("Skipping alteration test: " + altType.toString());
- continue;
- }
-
- System.out.println("Testing alteration: " + altType.toString());
- SecureMessage modified = modifyMessage(encryptedAndSigned, altType);
- try {
- verifyDecrypt(modified, sigType, encType);
- fail();
- } catch (SignatureException e) {
- // We expect this
- }
- }
- System.out.println("PASS_TEST -- Testing SigType: " + sigType + " EncType: " + encType);
- }
-
- private boolean skipAlterationTestFor(Alteration altType, SigType sigType) {
- // The RESIGNCRYPTION_ATTACK may be allowed to succeed iff the same symmetric key
- // is being reused for both signature and encryption.
- return (altType == Alteration.RESIGNCRYPTION_ATTACK)
- // Intentionally testing equality of object address here
- && (getVerificationKeyFor(sigType) == aesEncryptionKey);
- }
-
- private SecureMessage modifyMessage(SecureMessage original, Alteration altType) throws Exception {
- ByteString bogus = ByteString.copyFromUtf8("BOGUS");
- HeaderAndBody origHAB = HeaderAndBody.parseFrom(original.getHeaderAndBody());
- HeaderAndBody.Builder newHAB = HeaderAndBody.newBuilder(origHAB);
- Header.Builder newHeader = Header.newBuilder(origHAB.getHeader());
- Header origHeader = origHAB.getHeader();
- SecureMessage.Builder result = SecureMessage.newBuilder(original);
- switch (altType) {
- case DECRYPTION_KEY_ID:
- if (origHeader.hasDecryptionKeyId()) {
- newHeader.clearDecryptionKeyId();
- } else {
- newHeader.setDecryptionKeyId(ByteString.copyFrom(TEST_KEY_ID));
- }
- break;
- case ENCTYPE:
- if (origHeader.getEncryptionScheme() == SecureMessageProto.EncScheme.NONE) {
- newHeader.setEncryptionScheme(SecureMessageProto.EncScheme.AES_256_CBC);
- } else {
- newHeader.setEncryptionScheme(SecureMessageProto.EncScheme.NONE);
- }
- break;
- case HEADER_AND_BODY_PROTO:
- // Substitute a junk byte string instead of the HeeaderAndBody proto message
- return result.setHeaderAndBody(bogus).build();
- case MESSAGE:
- byte[] origBody = origHAB.getBody().toByteArray();
- if (origBody.length > 0) {
- // Lop off trailing byte of the body
- byte[] truncatedBody = CryptoOps.subarray(origBody, 0, origBody.length - 1);
- newHAB.setBody(ByteString.copyFrom(truncatedBody));
- } else {
- newHAB.setBody(bogus);
- }
- break;
- case METADATA:
- if (origHeader.hasPublicMetadata()) {
- newHeader.clearPublicMetadata();
- } else {
- newHeader.setPublicMetadata(bogus);
- }
- break;
- case RESIGNCRYPTION_ATTACK:
- // Simulate stripping a signature, and re-signing a message to see if it will be decrypted.
- newHeader
- .setVerificationKeyId(bogus)
- // In case original was cleartext
- .setEncryptionScheme(SecureMessageProto.EncScheme.AES_256_CBC);
-
- // Now that we've mildly changed the header, compute a new signature for it.
- newHAB.setHeader(newHeader.build());
- byte[] headerAndBodyBytes = newHAB.build().toByteArray();
- result.setHeaderAndBody(ByteString.copyFrom(headerAndBodyBytes));
- SigType sigType = SigType.valueOf(origHeader.getSignatureScheme());
- // Note that in all cases where this attack applies, the associatedData is not normally
- // used directly inside the signature (but rather inside the inner ciphertext).
- result.setSignature(ByteString.copyFrom(CryptoOps.sign(
- sigType, getSigningKeyFor(sigType), rng, headerAndBodyBytes)));
- return result.build();
- case SIGTYPE:
- if (origHeader.getSignatureScheme() == SecureMessageProto.SigScheme.ECDSA_P256_SHA256) {
- newHeader
- .setSignatureScheme(SecureMessageProto.SigScheme.HMAC_SHA256);
- } else {
- newHeader
- .setSignatureScheme(SecureMessageProto.SigScheme.ECDSA_P256_SHA256);
- }
- break;
- case VERIFICATION_KEY_ID:
- if (origHeader.hasVerificationKeyId()) {
- newHeader.clearVerificationKeyId();
- } else {
- newHeader.setVerificationKeyId(
- ByteString.copyFrom(TEST_KEY_ID));
- }
- break;
- case ASSOCIATED_DATA_LENGTH:
- int adLength = origHeader.getAssociatedDataLength();
- switch (adLength) {
- case 0:
- newHeader.setAssociatedDataLength(1);
- break;
- case 1:
- newHeader.setAssociatedDataLength(0);
- break;
- default:
- newHeader.setAssociatedDataLength(adLength - 1);
- }
- break;
- default:
- fail("Forgot to implement an alteration attack: " + altType);
- break;
- }
- // Set the header.
- newHAB.setHeader(newHeader.build());
-
- return result.setHeaderAndBody(ByteString.copyFrom(newHAB.build().toByteArray()))
- .build();
- }
-
- public void testEcDsaSignedOnly() throws Exception {
- doTestSignedOnly(SigType.ECDSA_P256_SHA256);
- }
-
- public void testRsaSignedOnly() throws Exception {
- doTestSignedOnly(SigType.RSA2048_SHA256);
- }
-
- public void testHmacSignedOnly() throws Exception {
- doTestSignedOnly(SigType.HMAC_SHA256);
- }
-
- private void doTestSignedOnly(SigType sigType) throws Exception {
- if (isUnsupported(sigType)) {
- return;
- }
-
- // decryptionKeyId must be left null for signature-only operation
- for (byte[] vkId : KEY_ID_VALUES) {
- verificationKeyId = vkId;
- for (byte[] md : METADATA_VALUES) {
- metadata = md;
- for (byte[] ad : ASSOCIATED_DATA_VALUES) {
- associatedData = ad;
- for (byte[] msg : MESSAGE_VALUES) {
- message = msg;
- doSignAndVerify(sigType);
- }
- }
- }
- }
-
- // Test that use of a DecryptionKeyId is not allowed for signature-only
- try {
- decryptionKeyId = TEST_KEY_ID; // Should trigger a failure
- doSignAndVerify(sigType);
- fail();
- } catch (IllegalStateException expected) {
- }
-}
-
- public void testEncryptedAndMACed() throws Exception {
- for (byte[] dkId : KEY_ID_VALUES) {
- decryptionKeyId = dkId;
- for (byte[] vkId : KEY_ID_VALUES) {
- verificationKeyId = vkId;
- for (byte[] md : METADATA_VALUES) {
- metadata = md;
- for (byte[] ad : ASSOCIATED_DATA_VALUES) {
- associatedData = ad;
- for (byte[] msg : MESSAGE_VALUES) {
- message = msg;
- doSignCryptAndVerifyDecrypt(SigType.HMAC_SHA256);
- }
- }
- }
- }
- }
- }
-
- public void testEncryptedAndMACedWithSameKey() throws Exception {
- hmacKey = aesEncryptionKey; // Re-use the same key for both
- testEncryptedAndMACed();
- }
-
- public void testEncryptedAndEcdsaSigned() throws Exception {
- doTestEncryptedAndSigned(SigType.ECDSA_P256_SHA256);
- }
-
- public void testEncryptedAndRsaSigned() throws Exception {
- doTestEncryptedAndSigned(SigType.RSA2048_SHA256);
- }
-
- public void doTestEncryptedAndSigned(SigType sigType) throws Exception {
- if (isUnsupported(sigType)) {
- return; // EC operations aren't supported on older Android releases
- }
-
- for (byte[] dkId : KEY_ID_VALUES) {
- decryptionKeyId = dkId;
- for (byte[] vkId : KEY_ID_VALUES) {
- verificationKeyId = vkId;
- if ((verificationKeyId == null) && sigType.isPublicKeyScheme()) {
- continue; // Null verificationKeyId is not allowed with public key signcryption
- }
- for (byte[] md : METADATA_VALUES) {
- metadata = md;
- for (byte[] ad : ASSOCIATED_DATA_VALUES) {
- associatedData = ad;
- for (byte[] msg : MESSAGE_VALUES) {
- message = msg;
- doSignCryptAndVerifyDecrypt(sigType);
- }
- }
- }
- }
- }
-
- // Verify that a missing verificationKeyId is not allowed here
- try {
- verificationKeyId = null; // Should trigger a failure
- signCrypt(sigType, EncType.AES_256_CBC);
- fail();
- } catch (IllegalStateException expected) {
- }
- }
-
- public void testSignCryptionRequiresEncryption() throws Exception {
- try {
- signCrypt(SigType.RSA2048_SHA256, EncType.NONE);
- } catch (IllegalArgumentException expected) {
- }
- }
-
- public void testAssociatedData() throws Exception {
- // How much extra room might the encoding of AssociatedDataLength take up?
- int maxAssociatedDataOverheadBytes = 4;
- // How many bytes might normally vary in the encoding length for SecureMessages generated with
- // fresh randomness but identical contents (e.g., due to MSBs being 0)
- int maxJitter = 2;
- verificationKeyId = TEST_KEY_ID; // So that public key signcryption will work
- message = TEST_MESSAGE;
-
- for (SigType sigType : SigType.values()) {
- if (isUnsupported(sigType)) {
- continue;
- }
- associatedData = null;
- SecureMessage signed = sign(sigType);
- int signedLength = signed.toByteArray().length;
- associatedData = EMPTY_BYTES;
- // Check that EMPTY_BYTES is equivalent to null associated data under verification
- verify(signed, sigType);
- // We already tested that incorrect associated data fails elsewhere in negative test cases
- associatedData = TEST_ASSOCIATED_DATA;
- SecureMessage signedWithAssociatedData = sign(sigType);
- int signedWithAssociatedDataLength = signedWithAssociatedData.toByteArray().length;
- String logInfo = "Testing associated data overhead for signature using: " + sigType
- + " signedLength=" + signedLength
- + " signedWithAssociatedDataLength=" + signedWithAssociatedDataLength;
- System.out.println(logInfo);
- assertTrue(logInfo,
- signedWithAssociatedData.toByteArray().length
- <= signed.toByteArray().length + maxAssociatedDataOverheadBytes + maxJitter);
- }
-
- for (SigType sigType : SigType.values()) {
- if (isUnsupported(sigType)) {
- continue;
- }
- associatedData = null;
- SecureMessage signCrypted = signCrypt(sigType, EncType.AES_256_CBC);
- int signCryptedLength = signCrypted.toByteArray().length;
- // Check that EMPTY_BYTES is equivalent to null associated data under verification
- associatedData = EMPTY_BYTES;
- verifyDecrypt(signCrypted, sigType, EncType.AES_256_CBC);
- // We already tested that incorrect associated data fails elsewhere in negative test cases
- associatedData = TEST_ASSOCIATED_DATA;
- SecureMessage signCryptedWithAssociatedData = signCrypt(sigType, EncType.AES_256_CBC);
- int signCryptedWithAssociatedDataLength = signCryptedWithAssociatedData.toByteArray().length;
- String logInfo = "Testing associated data overhead for signcryption using: " + sigType
- + " signCryptedLength=" + signCryptedLength
- + " signCryptedWithAssociatedDataLength=" + signCryptedWithAssociatedDataLength;
- System.out.println(logInfo);
- assertTrue(logInfo,
- signCryptedWithAssociatedData.toByteArray().length
- <= signCrypted.toByteArray().length + maxAssociatedDataOverheadBytes + maxJitter);
- }
- }
-
- public void testEncryptedAndEcdsaSignedUsingPublicKeyProto() throws Exception {
- if (isUnsupported(SigType.ECDSA_P256_SHA256)) {
- return;
- }
-
- // Safest usage of SignCryption is to set the VerificationKeyId to an actual representation of
- // the verification key.
- verificationKeyId = PublicKeyProtoUtil.encodeEcPublicKey(ecPublicKey).toByteArray();
- SecureMessage encryptedAndSigned = signCrypt(SigType.ECDSA_P256_SHA256, EncType.AES_256_CBC);
-
- // Simulate extracting the verification key ID from the SecureMessage (non-standard usage)
- ecPublicKey =
- PublicKeyProtoUtil.parseEcPublicKey(
- EcP256PublicKey.parseFrom(
- SecureMessageParser.getUnverifiedHeader(encryptedAndSigned)
- .getVerificationKeyId()));
-
- // Note that this verification uses the encoded/decoded ecPublicKey value
- verifyDecrypt(encryptedAndSigned, SigType.ECDSA_P256_SHA256, EncType.AES_256_CBC);
- }
-
- public void testEncryptedAndRsaSignedUsingPublicKeyProto() throws Exception {
- // Safest usage of SignCryption is to set the VerificationKeyId to an actual representation of
- // the verification key.
- verificationKeyId = PublicKeyProtoUtil.encodeRsa2048PublicKey(rsaPublicKey).toByteArray();
- SecureMessage encryptedAndSigned = signCrypt(SigType.RSA2048_SHA256, EncType.AES_256_CBC);
-
- // Simulate extracting the verification key ID from the SecureMessage (non-standard usage)
- rsaPublicKey =
- PublicKeyProtoUtil.parseRsa2048PublicKey(
- SimpleRsaPublicKey.parseFrom(
- SecureMessageParser.getUnverifiedHeader(encryptedAndSigned)
- .getVerificationKeyId()));
-
- // Note that this verification uses the encoded/decoded SimpleRsaPublicKey value
- verifyDecrypt(encryptedAndSigned, SigType.RSA2048_SHA256, EncType.AES_256_CBC);
- }
-
- // TODO(shabsi): The test was only corrupting header but wasn't setting the body. With protolite,
- // not setting a required field causes problems. Modify the SecureMessageParser test and
- // enable/remove this test.
- /*
- public void testCorruptUnverifiedHeader() throws Exception {
- // Create a sample message
- SecureMessage original = signCrypt(SigType.HMAC_SHA256, EncType.AES_256_CBC);
- HeaderAndBody originalHAB = HeaderAndBody.parseFrom(original.getHeaderAndBody().toByteArray());
- for (CorruptHeaderType corruptionType : CorruptHeaderType.values()) {
- // Mess with the HeaderAndBody field
- HeaderAndBody.Builder corruptHAB = HeaderAndBody.newBuilder(originalHAB);
- try {
- corruptHeaderWith(corruptionType, corruptHAB);
- // Construct the corrupted message using the modified HeaderAndBody
- SecureMessage.Builder corrupt = SecureMessage.newBuilder(original);
- corrupt.setHeaderAndBody(ByteString.copyFrom(corruptHAB.build().toByteArray())).build();
- SecureMessageParser.getUnverifiedHeader(corrupt.build());
- fail("Corrupt header type " + corruptionType + " parsed without error");
- } catch (InvalidProtocolBufferException expected) {
- }
- }
- }
- */
-
- public void testParseEmptyMessage() throws Exception {
- byte[] bogusData = new byte[0];
-
- try {
- SecureMessageParser.parseSignedCleartextMessage(
- SecureMessage.parseFrom(bogusData),
- aesEncryptionKey,
- SigType.HMAC_SHA256);
- fail("Empty message verified without error");
- } catch (SignatureException | UninitializedMessageException
- | InvalidProtocolBufferException expected) {
- }
- }
-
- public void testParseKeyInvalidInputs() throws Exception {
- GenericPublicKey[] badKeys = new GenericPublicKey[] {
- GenericPublicKey.newBuilder().setType(SecureMessageProto.PublicKeyType.EC_P256).build(),
- GenericPublicKey.newBuilder().setType(SecureMessageProto.PublicKeyType.RSA2048).build(),
- GenericPublicKey.newBuilder().setType(SecureMessageProto.PublicKeyType.DH2048_MODP).build(),
- };
- for (int i = 0; i < badKeys.length; i++) {
- GenericPublicKey key = badKeys[i];
- try {
- PublicKeyProtoUtil.parsePublicKey(key);
- fail(String.format("%sth key was parsed without exceptions", i));
- } catch (InvalidKeySpecException expected) {
- }
- }
- }
-
- enum CorruptHeaderType {
- EMPTY,
- // TODO(shabsi): Remove these test cases and modify code in SecureMessageParser appropriately.
- // UNSET,
- // JUNK,
- }
-
- private void corruptHeaderWith(CorruptHeaderType corruptionType,
- HeaderAndBody.Builder protoToModify) {
- switch (corruptionType) {
- case EMPTY:
- protoToModify.setHeader(Header.getDefaultInstance());
- break;
- /*
- case JUNK:
- Header.Builder junk = Header.newBuilder();
- junk.setDecryptionKeyId(ByteString.copyFromUtf8("fooooo"));
- junk.setIv(ByteString.copyFromUtf8("bar"));
- // Don't set signature scheme.
- junk.setVerificationKeyId(ByteString.copyFromUtf8("bazzzzz"));
- protoToModify.setHeader(junk.build());
- break;
- case UNSET:
- protoToModify.clearHeader();
- break;
- */
- default:
- throw new RuntimeException("Broken test code");
- }
- }
-
- private void consistencyCheck(
- SecureMessage secmsg, HeaderAndBody headerAndBody, SigType sigType, EncType encType)
- throws InvalidProtocolBufferException {
- Header header = SecureMessageParser.getUnverifiedHeader(secmsg);
- checkHeader(header, sigType, encType); // Checks that the "unverified header" looks right
- checkHeaderAndBody(header, headerAndBody); // Matches header vs. the "verified" headerAndBody
- }
-
- private Header checkHeader(Header header, SigType sigType, EncType encType) {
- assertEquals(sigType.getSigScheme(), header.getSignatureScheme());
- assertEquals(encType.getEncScheme(), header.getEncryptionScheme());
- checkKeyIdsAndMetadata(verificationKeyId, decryptionKeyId, metadata, associatedData, header);
- return header;
- }
-
- private void checkHeaderAndBody(Header header, HeaderAndBody headerAndBody) {
- assertTrue(header.equals(headerAndBody.getHeader()));
- assertTrue(Arrays.equals(message, headerAndBody.getBody().toByteArray()));
- }
-
- private void checkKeyIdsAndMetadata(byte[] verificationKeyId, byte[] decryptionKeyId,
- byte[] metadata, byte[] associatedData, Header header) {
- if (verificationKeyId == null) {
- assertFalse(header.hasVerificationKeyId());
- } else {
- assertTrue(Arrays.equals(verificationKeyId, header.getVerificationKeyId().toByteArray()));
- }
- if (decryptionKeyId == null) {
- assertFalse(header.hasDecryptionKeyId());
- } else {
- assertTrue(Arrays.equals(decryptionKeyId, header.getDecryptionKeyId().toByteArray()));
- }
- if (metadata == null) {
- assertFalse(header.hasPublicMetadata());
- } else {
- assertTrue(Arrays.equals(metadata, header.getPublicMetadata().toByteArray()));
- }
- if (associatedData == null) {
- assertFalse(header.hasAssociatedDataLength());
- } else {
- assertEquals(associatedData.length, header.getAssociatedDataLength());
- }
- }
-
- private SecretKey makeAesKey() throws NoSuchAlgorithmException {
- KeyGenerator aesKeygen = KeyGenerator.getInstance("AES");
- aesKeygen.init(256);
- return aesKeygen.generateKey();
- }
-
- private Key getSigningKeyFor(SigType sigType) {
- if (sigType == SigType.ECDSA_P256_SHA256) {
- return ecPrivateKey;
- }
- if (sigType == SigType.RSA2048_SHA256) {
- return rsaPrivateKey;
- }
- if (sigType == SigType.HMAC_SHA256) {
- return hmacKey;
- }
- return null; // This should not happen
- }
-
- private Key getVerificationKeyFor(SigType sigType) {
- try {
- if (sigType == SigType.ECDSA_P256_SHA256) {
- return PublicKeyProtoUtil.parseEcPublicKey(
- PublicKeyProtoUtil.encodeEcPublicKey(ecPublicKey));
- }
- if (sigType == SigType.RSA2048_SHA256) {
- return PublicKeyProtoUtil.parseRsa2048PublicKey(
- PublicKeyProtoUtil.encodeRsa2048PublicKey(rsaPublicKey));
- }
- } catch (InvalidKeySpecException e) {
- throw new AssertionError(e);
- }
-
- assertFalse(sigType.isPublicKeyScheme());
- // For symmetric key schemes
- return getSigningKeyFor(sigType);
- }
-
- private SecureMessageBuilder getPreconfiguredBuilder() {
- // Re-use a single instance of SecureMessageBuilder for efficiency.
- SecureMessageBuilder builder = secureMessageBuilder.reset();
- if (verificationKeyId != null) {
- builder.setVerificationKeyId(verificationKeyId);
- }
- if (decryptionKeyId != null) {
- builder.setDecryptionKeyId(decryptionKeyId);
- }
- if (metadata != null) {
- builder.setPublicMetadata(metadata);
- }
- if (associatedData != null) {
- builder.setAssociatedData(associatedData);
- }
- return builder;
- }
-
- private static boolean isUnsupported(SigType sigType) {
- // EC operations aren't supported on older Android releases
- return PublicKeyProtoUtil.isLegacyCryptoRequired()
- && (sigType == SigType.ECDSA_P256_SHA256);
- }
-
- private static boolean isRunningInAndroid() {
- try {
- ClassLoader.getSystemClassLoader().loadClass("android.os.Build$VERSION");
- return true;
- } catch (ClassNotFoundException e) {
- // Not running on Android
- return false;
- }
- }
-}
diff --git a/src/main/proto/CMakeLists.txt b/src/main/proto/CMakeLists.txt
deleted file mode 100644
index cd94f3f..0000000
--- a/src/main/proto/CMakeLists.txt
+++ /dev/null
@@ -1,32 +0,0 @@
-# Copyright 2020 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
-#
-# https://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.
-
-add_cc_proto_library(
- proto_device_to_device_messages_cc_proto
- PROTOS device_to_device_messages.proto
- DEPS proto_securemessage_cc_proto
- INCS ${CMAKE_CURRENT_BINARY_DIR}/..
-)
-
-add_cc_proto_library(
- proto_securegcm_cc_proto
- PROTOS securegcm.proto
- INCS ${CMAKE_CURRENT_BINARY_DIR}/..
-)
-
-add_cc_proto_library(
- proto_ukey_cc_proto
- PROTOS ukey.proto
- INCS ${CMAKE_CURRENT_BINARY_DIR}/..
-)
diff --git a/src/main/proto/device_to_device_messages.proto b/src/main/proto/device_to_device_messages.proto
deleted file mode 100644
index 5600373..0000000
--- a/src/main/proto/device_to_device_messages.proto
+++ /dev/null
@@ -1,81 +0,0 @@
-// Copyright 2020 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
-//
-// https://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.
-
-syntax = "proto2";
-
-package securegcm;
-
-import "securemessage.proto";
-
-option optimize_for = LITE_RUNTIME;
-option java_package = "com.google.security.cryptauth.lib.securegcm";
-option java_outer_classname = "DeviceToDeviceMessagesProto";
-option objc_class_prefix = "SGCM";
-
-// Used by protocols between devices
-message DeviceToDeviceMessage {
- // the payload of the message
- optional bytes message = 1;
-
- // the sequence number of the message - must be increasing.
- optional int32 sequence_number = 2;
-}
-
-// sent as the first message from initiator to responder
-// in an unauthenticated Diffie-Hellman Key Exchange
-message InitiatorHello {
- // The session public key to send to the responder
- optional securemessage.GenericPublicKey public_dh_key = 1;
-
- // The protocol version
- optional int32 protocol_version = 2 [default = 0];
-}
-
-// sent inside the header of the first message from the responder to the
-// initiator in an unauthenticated Diffie-Hellman Key Exchange
-message ResponderHello {
- // The session public key to send to the initiator
- optional securemessage.GenericPublicKey public_dh_key = 1;
-
- // The protocol version
- optional int32 protocol_version = 2 [default = 0];
-}
-
-// Type of curve
-enum Curve { ED_25519 = 1; }
-
-// A convenience proto for encoding curve points in affine representation
-message EcPoint {
- required Curve curve = 1;
-
- // x and y are encoded in big-endian two's complement
- // client MUST verify (x,y) is a valid point on the specified curve
- required bytes x = 2;
- required bytes y = 3;
-}
-
-message SpakeHandshakeMessage {
- // Each flow in the protocol bumps this counter
- optional int32 flow_number = 1;
-
- // Some (but not all) SPAKE flows send a point on an elliptic curve
- optional EcPoint ec_point = 2;
-
- // Some (but not all) SPAKE flows send a hash value
- optional bytes hash_value = 3;
-
- // The last flow of a SPAKE protocol can send an optional payload,
- // since the key exchange is already complete on the sender's side.
- optional bytes payload = 4;
-}
diff --git a/src/main/proto/passwordless_auth_payloads.proto b/src/main/proto/passwordless_auth_payloads.proto
deleted file mode 100644
index 69c2784..0000000
--- a/src/main/proto/passwordless_auth_payloads.proto
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2020 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
-//
-// https://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.
-
-syntax = "proto2";
-
-package securegcm;
-
-option optimize_for = LITE_RUNTIME;
-option java_package = "com.google.security.cryptauth.lib.securegcm";
-option java_outer_classname = "SecureGcmPasswordlessAuthProto";
-option objc_class_prefix = "SGCM";
-
-message IdentityAssertion {
- // Browser data contains the challenge, origin, etc.
- optional bytes browser_data_hash = 1;
-
- // A counter that we expect to increase.
- optional int64 counter = 2;
-
- // An integer encoding whether the user actively approved this assertion,
- // or whether the phone auto-issued the assertion.
- // Possible values are:
- // 1: User explicitly approved the login.
- // 0: Phone approved login without consulting the user.
- optional int32 user_approval = 3;
-}
diff --git a/src/main/proto/proximity_payloads.proto b/src/main/proto/proximity_payloads.proto
deleted file mode 100644
index d7a4956..0000000
--- a/src/main/proto/proximity_payloads.proto
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright 2020 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
-//
-// https://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.
-
-syntax = "proto2";
-
-package securegcm;
-
-import "securegcm.proto";
-
-option optimize_for = LITE_RUNTIME;
-option java_package = "com.google.security.cryptauth.lib.securegcm";
-option java_outer_classname = "SecureGcmProximityAuthProto";
-option objc_class_prefix = "SGCM";
-
-// Message used when one device wants to initiate a Proximity Auth pairing with
-// another device DEPRECATED. DO NOT USE
-message CloudToDeviceProximityAuthPairing {
- // The name or description of the device that wants to pair with another
- // personal device of the user. This is a string that may be shown to the
- // user or may be kept in logs.
- optional string initiating_device_name = 1;
-
- // The original device's Bluetooth address in human readable form
- // (e.g., <code>AA:BB:CC:DD:EE:FF</code>)
- optional string initiating_device_bt_address = 2;
-
- // A symmetric key that was generated by the original device.
- optional bytes ephemeral_symmetric_key = 3;
-
- // Optional additional metadata that the initiating device can choose to send.
- // Used for quick protocol iteration.
- optional bytes additional_metadata = 4;
-}
-
-// Message to push to eligible unlock devices so that they can contact the
-// device to be unlocked. Used by FindEligibleUnlockDevicesRequest, with
-// PayloadType = DEVICE_PROXIMITY_CALLBACK.
-message DeviceProximityCallback {
- // Required. The bluetooth MAC address that should be contacted by the unlock
- // device.
- optional string callback_bluetooth_address = 1;
-
- // Required. The type of the device that triggered this callback to be sent.
- optional DeviceType source_device_type = 2;
-
- // The version of the setup protocol that the source device expects to use.
- optional int32 protocol_version = 3;
-}
diff --git a/src/main/proto/securegcm.proto b/src/main/proto/securegcm.proto
deleted file mode 100644
index 0325f06..0000000
--- a/src/main/proto/securegcm.proto
+++ /dev/null
@@ -1,308 +0,0 @@
-// Copyright 2020 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
-//
-// https://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.
-
-syntax = "proto2";
-
-package securegcm;
-
-option optimize_for = LITE_RUNTIME;
-option java_package = "com.google.security.cryptauth.lib.securegcm";
-option java_outer_classname = "SecureGcmProto";
-option objc_class_prefix = "SGCM";
-
-// Message used only during enrollment
-// Field numbers should be kept in sync with DeviceInfo in:
-// java/com/google/security/cryptauth/backend/services/common/common.proto
-message GcmDeviceInfo {
- // This field's name does not match the one in DeviceInfo for legacy reasons.
- // Consider using long_device_id and device_type instead when enrolling
- // non-android devices.
- optional fixed64 android_device_id = 1;
-
- // Used for device_address of DeviceInfo field 2, but for GCM capable devices.
- optional bytes gcm_registration_id = 102;
-
- // Used for device_address of DeviceInfo field 2, but for iOS devices.
- optional bytes apn_registration_id = 202;
-
- // Does the user have notifications enabled for the given device address.
- optional bool notification_enabled = 203 [default = true];
-
- // Used for device_address of DeviceInfo field 2, a Bluetooth Mac address for
- // the device (e.g., to be used with EasyUnlock)
- optional string bluetooth_mac_address = 302;
-
- // SHA-256 hash of the device master key (from the key exchange).
- // Differs from DeviceInfo field 3, which contains the actual master key.
- optional bytes device_master_key_hash = 103;
-
- // A SecureMessage.EcP256PublicKey
- required bytes user_public_key = 4;
-
- // device's model name
- // (e.g., an android.os.Build.MODEL or UIDevice.model)
- optional string device_model = 7;
-
- // device's locale
- optional string locale = 8;
-
- // The handle for user_public_key (and implicitly, a master key)
- optional bytes key_handle = 9;
-
- // The initial counter value for the device, sent by the device
- optional int64 counter = 12 [default = 0];
-
- // The Operating System version on the device
- // (e.g., an android.os.Build.DISPLAY or UIDevice.systemVersion)
- optional string device_os_version = 13;
-
- // The Operating System version number on the device
- // (e.g., an android.os.Build.VERSION.SDK_INT)
- optional int64 device_os_version_code = 14;
-
- // The Operating System release on the device
- // (e.g., an android.os.Build.VERSION.RELEASE)
- optional string device_os_release = 15;
-
- // The Operating System codename on the device
- // (e.g., an android.os.Build.VERSION.CODENAME or UIDevice.systemName)
- optional string device_os_codename = 16;
-
- // The software version running on the device
- // (e.g., Authenticator app version string)
- optional string device_software_version = 17;
-
- // The software version number running on the device
- // (e.g., Authenticator app version code)
- optional int64 device_software_version_code = 18;
-
- // Software package information if applicable
- // (e.g., com.google.android.apps.authenticator2)
- optional string device_software_package = 19;
-
- // Size of the display in thousandths of an inch (e.g., 7000 mils = 7 in)
- optional int32 device_display_diagonal_mils = 22;
-
- // For Authzen capable devices, their Authzen protocol version
- optional int32 device_authzen_version = 24;
-
- // Not all devices have device identifiers that fit in 64 bits.
- optional bytes long_device_id = 29;
-
- // The device manufacturer name
- // (e.g., android.os.Build.MANUFACTURER)
- optional string device_manufacturer = 31;
-
- // Used to indicate which type of device this is.
- optional DeviceType device_type = 32 [default = ANDROID];
-
- // Fields corresponding to screenlock type/features and hardware features
- // should be numbered in the 400 range.
-
- // Is this device using a secure screenlock (e.g., pattern or pin unlock)
- optional bool using_secure_screenlock = 400 [default = false];
-
- // Is auto-unlocking the screenlock (e.g., when at "home") supported?
- optional bool auto_unlock_screenlock_supported = 401 [default = false];
-
- // Is auto-unlocking the screenlock (e.g., when at "home") enabled?
- optional bool auto_unlock_screenlock_enabled = 402 [default = false];
-
- // Does the device have a Bluetooth (classic) radio?
- optional bool bluetooth_radio_supported = 403 [default = false];
-
- // Is the Bluetooth (classic) radio on?
- optional bool bluetooth_radio_enabled = 404 [default = false];
-
- // Does the device hardware support a mobile data connection?
- optional bool mobile_data_supported = 405 [default = false];
-
- // Does the device support tethering?
- optional bool tethering_supported = 406 [default = false];
-
- // Does the device have a BLE radio?
- optional bool ble_radio_supported = 407 [default = false];
-
- // Is the device a "Pixel Experience" Android device?
- optional bool pixel_experience = 408 [default = false];
-
- // Is the device running in the ARC++ container on a chromebook?
- optional bool arc_plus_plus = 409 [default = false];
-
- // Is the value set in |using_secure_screenlock| reliable? On some Android
- // devices, the platform API to get the screenlock state is not trustworthy.
- // See b/32212161.
- optional bool is_screenlock_state_flaky = 410 [default = false];
-
- // A list of multi-device software features supported by the device.
- repeated SoftwareFeature supported_software_features = 411;
-
- // A list of multi-device software features currently enabled (active) on the
- // device.
- repeated SoftwareFeature enabled_software_features = 412;
-
- // The enrollment session id this is sent with
- optional bytes enrollment_session_id = 1000;
-
- // A copy of the user's OAuth token
- optional string oauth_token = 1001;
-}
-
-// This enum is used by iOS devices as values for device_display_diagonal_mils
-// in GcmDeviceInfo. There is no good way to calculate it on those devices.
-enum AppleDeviceDiagonalMils {
- // This is the mils diagonal on an iPhone 5.
- APPLE_PHONE = 4000;
- // This is the mils diagonal on an iPad mini.
- APPLE_PAD = 7900;
-}
-
-// This should be kept in sync with DeviceType in:
-// java/com/google/security/cryptauth/backend/services/common/common_enums.proto
-enum DeviceType {
- UNKNOWN = 0;
- ANDROID = 1;
- CHROME = 2;
- IOS = 3;
- BROWSER = 4;
- OSX = 5;
-}
-
-// MultiDevice features which may be supported and enabled on a device. See
-enum SoftwareFeature {
- UNKNOWN_FEATURE = 0;
- BETTER_TOGETHER_HOST = 1;
- BETTER_TOGETHER_CLIENT = 2;
- EASY_UNLOCK_HOST = 3;
- EASY_UNLOCK_CLIENT = 4;
- MAGIC_TETHER_HOST = 5;
- MAGIC_TETHER_CLIENT = 6;
- SMS_CONNECT_HOST = 7;
- SMS_CONNECT_CLIENT = 8;
-}
-
-// A list of "reasons" that can be provided for calling server-side APIs.
-// This is particularly important for calls that can be triggered by different
-// kinds of events. Please try to keep reasons as generic as possible, so that
-// codes can be re-used by various callers in a sensible fashion.
-enum InvocationReason {
- REASON_UNKNOWN = 0;
- // First run of the software package invoking this call
- REASON_INITIALIZATION = 1;
- // Ordinary periodic actions (e.g. monthly master key rotation)
- REASON_PERIODIC = 2;
- // Slow-cycle periodic action (e.g. yearly keypair rotation???)
- REASON_SLOW_PERIODIC = 3;
- // Fast-cycle periodic action (e.g. daily sync for Smart Lock users)
- REASON_FAST_PERIODIC = 4;
- // Expired state (e.g. expired credentials, or cached entries) was detected
- REASON_EXPIRATION = 5;
- // An unexpected protocol failure occurred (so attempting to repair state)
- REASON_FAILURE_RECOVERY = 6;
- // A new account has been added to the device
- REASON_NEW_ACCOUNT = 7;
- // An existing account on the device has been changed
- REASON_CHANGED_ACCOUNT = 8;
- // The user toggled the state of a feature (e.g. Smart Lock enabled via BT)
- REASON_FEATURE_TOGGLED = 9;
- // A "push" from the server caused this action (e.g. a sync tickle)
- REASON_SERVER_INITIATED = 10;
- // A local address change triggered this (e.g. GCM registration id changed)
- REASON_ADDRESS_CHANGE = 11;
- // A software update has triggered this
- REASON_SOFTWARE_UPDATE = 12;
- // A manual action by the user triggered this (e.g. commands sent via adb)
- REASON_MANUAL = 13;
- // A custom key has been invalidated on the device (e.g. screen lock is
- // disabled).
- REASON_CUSTOM_KEY_INVALIDATION = 14;
- // Periodic action triggered by auth_proximity
- REASON_PROXIMITY_PERIODIC = 15;
-}
-
-enum Type {
- ENROLLMENT = 0;
- TICKLE = 1;
- TX_REQUEST = 2;
- TX_REPLY = 3;
- TX_SYNC_REQUEST = 4;
- TX_SYNC_RESPONSE = 5;
- TX_PING = 6;
- DEVICE_INFO_UPDATE = 7;
- TX_CANCEL_REQUEST = 8;
-
- // DEPRECATED (can be re-used after Aug 2015)
- PROXIMITYAUTH_PAIRING = 10;
-
- // The kind of identity assertion generated by a "GCM V1" device (i.e.,
- // an Android phone that has registered with us a public and a symmetric
- // key)
- GCMV1_IDENTITY_ASSERTION = 11;
-
- // Device-to-device communications are protected by an unauthenticated
- // Diffie-Hellman exchange. The InitiatorHello message is simply the
- // initiator's public DH key, and is not encoded as a SecureMessage, so
- // it doesn't have a tag.
- // The ResponderHello message (which is sent by the responder
- // to the initiator), on the other hand, carries a payload that is protected
- // by the derived shared key. It also contains the responder's
- // public DH key. ResponderHelloAndPayload messages have the
- // DEVICE_TO_DEVICE_RESPONDER_HELLO tag.
- DEVICE_TO_DEVICE_RESPONDER_HELLO_PAYLOAD = 12;
-
- // Device-to-device communications are protected by an unauthenticated
- // Diffie-Hellman exchange. Once the initiator and responder
- // agree on a shared key (through Diffie-Hellman), they will use messages
- // tagged with DEVICE_TO_DEVICE_MESSAGE to exchange data.
- DEVICE_TO_DEVICE_MESSAGE = 13;
-
- // Notification to let a device know it should contact a nearby device.
- DEVICE_PROXIMITY_CALLBACK = 14;
-
- // Device-to-device communications are protected by an unauthenticated
- // Diffie-Hellman exchange. During device-to-device authentication, the first
- // message from initiator (the challenge) is signed and put into the payload
- // of the message sent back to the initiator.
- UNLOCK_KEY_SIGNED_CHALLENGE = 15;
-
- // Specialty (corp only) features
- LOGIN_NOTIFICATION = 101;
-}
-
-message GcmMetadata {
- required Type type = 1;
- optional int32 version = 2 [default = 0];
-}
-
-message Tickle {
- // Time after which this tickle should expire
- optional fixed64 expiry_time = 1;
-}
-
-message LoginNotificationInfo {
- // Time at which the server received the login notification request.
- optional fixed64 creation_time = 2;
-
- // Must correspond to user_id in LoginNotificationRequest, if set.
- optional string email = 3;
-
- // Host where the user's credentials were used to login, if meaningful.
- optional string host = 4;
-
- // Location from where the user's credentials were used, if meaningful.
- optional string source = 5;
-
- // Type of login, e.g. ssh, gnome-screensaver, or web.
- optional string event_type = 6;
-}
diff --git a/src/main/proto/securemessage.proto b/src/main/proto/securemessage.proto
deleted file mode 100644
index 5118d35..0000000
--- a/src/main/proto/securemessage.proto
+++ /dev/null
@@ -1,126 +0,0 @@
-// Copyright 2020 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
-//
-// https://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.
-
-// Proto definitions for SecureMessage format
-
-syntax = "proto2";
-
-package securemessage;
-
-option optimize_for = LITE_RUNTIME;
-option java_package = "com.google.security.cryptauth.lib.securemessage";
-option java_outer_classname = "SecureMessageProto";
-option objc_class_prefix = "SMSG";
-
-message SecureMessage {
- // Must contain a HeaderAndBody message
- required bytes header_and_body = 1;
- // Signature of header_and_body
- required bytes signature = 2;
-}
-
-// Supported "signature" schemes (both symmetric key and public key based)
-enum SigScheme {
- HMAC_SHA256 = 1;
- ECDSA_P256_SHA256 = 2;
- // Not recommended -- use ECDSA_P256_SHA256 instead
- RSA2048_SHA256 = 3;
-}
-
-// Supported encryption schemes
-enum EncScheme {
- // No encryption
- NONE = 1;
- AES_256_CBC = 2;
-}
-
-message Header {
- required SigScheme signature_scheme = 1;
- required EncScheme encryption_scheme = 2;
- // Identifies the verification key
- optional bytes verification_key_id = 3;
- // Identifies the decryption key
- optional bytes decryption_key_id = 4;
- // Encryption may use an IV
- optional bytes iv = 5;
- // Arbitrary per-protocol public data, to be sent with the plain-text header
- optional bytes public_metadata = 6;
- // The length of some associated data this is not sent in this SecureMessage,
- // but which will be bound to the signature.
- optional uint32 associated_data_length = 7 [default = 0];
-}
-
-message HeaderAndBody {
- // Public data about this message (to be bound in the signature)
- required Header header = 1;
- // Payload data
- required bytes body = 2;
-}
-
-// Must be kept wire-format compatible with HeaderAndBody. Provides the
-// SecureMessage code with a consistent wire-format representation that
-// remains stable irrespective of protobuf implementation choices. This
-// low-level representation of a HeaderAndBody should not be used by
-// any code outside of the SecureMessage library implementation/tests.
-message HeaderAndBodyInternal {
- // A raw (wire-format) byte encoding of a Header, suitable for hashing
- required bytes header = 1;
- // Payload data
- required bytes body = 2;
-}
-
-// -------
-// The remainder of the messages defined here are provided only for
-// convenience. They are not needed for SecureMessage proper, but are
-// commonly useful wherever SecureMessage might be applied.
-// -------
-
-// A list of supported public key types
-enum PublicKeyType {
- EC_P256 = 1;
- RSA2048 = 2;
- // 2048-bit MODP group 14, from RFC 3526
- DH2048_MODP = 3;
-}
-
-// A convenience proto for encoding NIST P-256 elliptic curve public keys
-message EcP256PublicKey {
- // x and y are encoded in big-endian two's complement (slightly wasteful)
- // Client MUST verify (x,y) is a valid point on NIST P256
- required bytes x = 1;
- required bytes y = 2;
-}
-
-// A convenience proto for encoding RSA public keys with small exponents
-message SimpleRsaPublicKey {
- // Encoded in big-endian two's complement
- required bytes n = 1;
- optional int32 e = 2 [default = 65537];
-}
-
-// A convenience proto for encoding Diffie-Hellman public keys,
-// for use only when Elliptic Curve based key exchanges are not possible.
-// (Note that the group parameters must be specified separately)
-message DhPublicKey {
- // Big-endian two's complement encoded group element
- required bytes y = 1;
-}
-
-message GenericPublicKey {
- required PublicKeyType type = 1;
- optional EcP256PublicKey ec_p256_public_key = 2;
- optional SimpleRsaPublicKey rsa2048_public_key = 3;
- // Use only as a last resort
- optional DhPublicKey dh2048_public_key = 4;
-}
diff --git a/src/main/proto/ukey.proto b/src/main/proto/ukey.proto
deleted file mode 100644
index 327d8d3..0000000
--- a/src/main/proto/ukey.proto
+++ /dev/null
@@ -1,105 +0,0 @@
-// Copyright 2020 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
-//
-// https://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.
-
-syntax = "proto2";
-
-package securegcm;
-
-option optimize_for = LITE_RUNTIME;
-option java_package = "com.google.security.cryptauth.lib.securegcm";
-option java_outer_classname = "UkeyProto";
-
-message Ukey2Message {
- enum Type {
- UNKNOWN_DO_NOT_USE = 0;
- ALERT = 1;
- CLIENT_INIT = 2;
- SERVER_INIT = 3;
- CLIENT_FINISH = 4;
- }
-
- optional Type message_type = 1; // Identifies message type
- optional bytes message_data = 2; // Actual message, to be parsed according to
- // message_type
-}
-
-message Ukey2Alert {
- enum AlertType {
- // Framing errors
- BAD_MESSAGE = 1; // The message could not be deserialized
- BAD_MESSAGE_TYPE = 2; // message_type has an undefined value
- INCORRECT_MESSAGE = 3; // message_type received does not correspond to
- // expected type at this stage of the protocol
- BAD_MESSAGE_DATA = 4; // Could not deserialize message_data as per
- // value inmessage_type
-
- // ClientInit and ServerInit errors
- BAD_VERSION = 100; // version is invalid; server cannot find
- // suitable version to speak with client.
- BAD_RANDOM = 101; // Random data is missing or of incorrect
- // length
- BAD_HANDSHAKE_CIPHER = 102; // No suitable handshake ciphers were found
- BAD_NEXT_PROTOCOL = 103; // The next protocol is missing, unknown, or
- // unsupported
- BAD_PUBLIC_KEY = 104; // The public key could not be parsed
-
- // Other errors
- INTERNAL_ERROR = 200; // An internal error has occurred. error_message
- // may contain additional details for logging
- // and debugging.
- }
-
- optional AlertType type = 1;
- optional string error_message = 2;
-}
-
-enum Ukey2HandshakeCipher {
- RESERVED = 0;
- P256_SHA512 = 100; // NIST P-256 used for ECDH, SHA512 used for
- // commitment
- CURVE25519_SHA512 = 200; // Curve 25519 used for ECDH, SHA512 used for
- // commitment
-}
-
-message Ukey2ClientInit {
- optional int32 version = 1; // highest supported version for rollback
- // protection
- optional bytes random = 2; // random bytes for replay/reuse protection
-
- // One commitment (hash of ClientFinished containing public key) per supported
- // cipher
- message CipherCommitment {
- optional Ukey2HandshakeCipher handshake_cipher = 1;
- optional bytes commitment = 2;
- }
- repeated CipherCommitment cipher_commitments = 3;
-
- // Next protocol that the client wants to speak.
- optional string next_protocol = 4;
-}
-
-message Ukey2ServerInit {
- optional int32 version = 1; // highest supported version for rollback
- // protection
- optional bytes random = 2; // random bytes for replay/reuse protection
-
- // Selected Cipher and corresponding public key
- optional Ukey2HandshakeCipher handshake_cipher = 3;
- optional bytes public_key = 4;
-}
-
-message Ukey2ClientFinished {
- optional bytes public_key = 1; // public key matching selected handshake
- // cipher
-}