summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexei Czeskis <aczeskis@google.com>2020-06-22 16:34:57 -0700
committerGitHub <noreply@github.com>2020-06-22 16:34:57 -0700
commit4550c84830286c24f8c189cbd54edbbd986a2dd1 (patch)
tree51dd4bc8823d45453df3afec57ebbde2d8dee692
parent8ed3a3675132bdebe8070a0744e574facc603de5 (diff)
parent65a1750598c4503cb22dd9dd6938f932fa4969c3 (diff)
downloadukey2-4550c84830286c24f8c189cbd54edbbd986a2dd1.tar.gz
Merge pull request #4 from apolyudov/master
Bring ukey2 up-to-date with internal code
-rw-r--r--.gitignore2
-rw-r--r--.gitmodules16
-rw-r--r--CMakeLists.txt50
-rw-r--r--METADATA12
-rw-r--r--README.md30
-rw-r--r--build.gradle14
-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/.gradle/3.2.1/taskArtifacts/fileSnapshots.binbin18577 -> 0 bytes
-rw-r--r--src/.gradle/3.2.1/taskArtifacts/taskArtifacts.binbin18786 -> 0 bytes
-rw-r--r--src/.gradle/3.2.1/taskArtifacts/taskArtifacts.lockbin17 -> 0 bytes
-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/proto/CMakeLists.txt32
-rw-r--r--src/main/proto/securegcm.proto14
m---------third_party/absl0
m---------third_party/gtest0
m---------third_party/protobuf0
m---------third_party/secure_message0
34 files changed, 2680 insertions, 17 deletions
diff --git a/.gitignore b/.gitignore
index 9d4d4f8..f53951a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,3 @@
build/**
.gradle/**
-
+.idea/**
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..2624f19
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,16 @@
+[submodule "third_party/secure_message"]
+ path = third_party/secure_message
+ url = https://github.com/google/securemessage
+ branch = master
+[submodule "third_party/gtest"]
+ path = third_party/gtest
+ url = https://github.com/google/googletest
+ branch = master
+[submodule "third_party/protobuf"]
+ path = third_party/protobuf
+ url = https://github.com/protocolbuffers/protobuf
+ branch = master
+[submodule "third_party/absl"]
+ path = third_party/absl
+ url = https://github.com/abseil/abseil-cpp
+ branch = master
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..02ddda2
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,50 @@
+# 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/METADATA b/METADATA
deleted file mode 100644
index 1548a33..0000000
--- a/METADATA
+++ /dev/null
@@ -1,12 +0,0 @@
-name: "Ukey2"
-description:
- "UKEY2 is a Diffie-Hellman based authenticated key exchange protocol."
-
-third_party {
- url {
- type: ARCHIVE
- value: "https://user.git.corp.google.com/michalp/ukey2/"
- }
- version: "1.0"
- last_upgrade_date { year: 2018 month: 12 day: 28 }
-}
diff --git a/README.md b/README.md
index 6e67394..87a652d 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,9 @@ This is not an officially supported Google product
**Coathored by:** Alexei Czeskis, Thai Duong, Eduardo' Vela'' \<Nava\>, and Adam Stubblefield.
-**Status:** Implemented in Java (aczeskis@google.com)
+**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
@@ -335,7 +337,29 @@ cd ukey2
git submodule update --init --recursive
```
-## Buillding Java library and run Java Tests
+# 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.
@@ -366,5 +390,3 @@ cd <source root>
```
This will build and execute all the tests.
-They are expected to pass, except for the c++ interworking tests which are expected to fail because
-c++ library is missing in this PR.
diff --git a/build.gradle b/build.gradle
index 2564533..e319422 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,3 +1,17 @@
+// 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'
diff --git a/cmake/local_build_protobuf.cmake b/cmake/local_build_protobuf.cmake
new file mode 100644
index 0000000..3a04d55
--- /dev/null
+++ b/cmake/local_build_protobuf.cmake
@@ -0,0 +1,42 @@
+# 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
new file mode 100644
index 0000000..a7917fe
--- /dev/null
+++ b/cmake/local_build_setup.cmake
@@ -0,0 +1,26 @@
+# 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
new file mode 100644
index 0000000..aae0ce9
--- /dev/null
+++ b/cmake/proto_defs.cmake
@@ -0,0 +1,42 @@
+# 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/.gradle/3.2.1/taskArtifacts/fileSnapshots.bin b/src/.gradle/3.2.1/taskArtifacts/fileSnapshots.bin
deleted file mode 100644
index f0cc1a8..0000000
--- a/src/.gradle/3.2.1/taskArtifacts/fileSnapshots.bin
+++ /dev/null
Binary files differ
diff --git a/src/.gradle/3.2.1/taskArtifacts/taskArtifacts.bin b/src/.gradle/3.2.1/taskArtifacts/taskArtifacts.bin
deleted file mode 100644
index b1f5426..0000000
--- a/src/.gradle/3.2.1/taskArtifacts/taskArtifacts.bin
+++ /dev/null
Binary files differ
diff --git a/src/.gradle/3.2.1/taskArtifacts/taskArtifacts.lock b/src/.gradle/3.2.1/taskArtifacts/taskArtifacts.lock
deleted file mode 100644
index 6fa2a90..0000000
--- a/src/.gradle/3.2.1/taskArtifacts/taskArtifacts.lock
+++ /dev/null
Binary files differ
diff --git a/src/main/CMakeLists.txt b/src/main/CMakeLists.txt
new file mode 100644
index 0000000..776826c
--- /dev/null
+++ b/src/main/CMakeLists.txt
@@ -0,0 +1,18 @@
+# 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
new file mode 100644
index 0000000..919e096
--- /dev/null
+++ b/src/main/cpp/CMakeLists.txt
@@ -0,0 +1,17 @@
+# 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
new file mode 100644
index 0000000..098e654
--- /dev/null
+++ b/src/main/cpp/include/securegcm/d2d_connection_context_v1.h
@@ -0,0 +1,89 @@
+// 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
new file mode 100644
index 0000000..eeeeb20
--- /dev/null
+++ b/src/main/cpp/include/securegcm/d2d_crypto_ops.h
@@ -0,0 +1,78 @@
+// 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
new file mode 100644
index 0000000..8783af4
--- /dev/null
+++ b/src/main/cpp/include/securegcm/java_util.h
@@ -0,0 +1,57 @@
+// 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
new file mode 100644
index 0000000..7e5973f
--- /dev/null
+++ b/src/main/cpp/include/securegcm/ukey2_handshake.h
@@ -0,0 +1,263 @@
+// 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:
+ // IN_PROGRESS:
+ // The handshake is in progress, caller should use
+ // |GetNextHandshakeMessage()| and |ParseHandshakeMessage()| to continue
+ // the handshake.
+ //
+ // VERIFICATION_NEEDED:
+ // 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.
+ //
+ // 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 |VerifyHandshake()| to mark the handshake as
+ // verified.
+ //
+ // FINISHED:
+ // The handshake is finished, and the caller can use
+ // |ToConnectionContext()| to produce a |D2DConnectionContextV1|.
+ //
+ // ALREADY_USED:
+ // The hanshake has already been used and should be destroyed.
+ //
+ // ERROR:
+ // The handshake produced an error and should be destroyed.
+ enum class 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:
+ // +-----------------------------------------------------+
+ // | 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
new file mode 100644
index 0000000..b562756
--- /dev/null
+++ b/src/main/cpp/src/securegcm/CMakeLists.txt
@@ -0,0 +1,47 @@
+# 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
new file mode 100644
index 0000000..8a9a612
--- /dev/null
+++ b/src/main/cpp/src/securegcm/d2d_connection_context_v1.cc
@@ -0,0 +1,228 @@
+// 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
new file mode 100644
index 0000000..49f0b85
--- /dev/null
+++ b/src/main/cpp/src/securegcm/d2d_crypto_ops.cc
@@ -0,0 +1,151 @@
+// 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
new file mode 100644
index 0000000..1ce4d7b
--- /dev/null
+++ b/src/main/cpp/src/securegcm/java_util.cc
@@ -0,0 +1,60 @@
+// 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
new file mode 100644
index 0000000..295331e
--- /dev/null
+++ b/src/main/cpp/src/securegcm/ukey2_handshake.cc
@@ -0,0 +1,715 @@
+// 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::IN_PROGRESS;
+ case InternalState::HANDSHAKE_VERIFICATION_NEEDED:
+ return State::VERIFICATION_NEEDED;
+ case InternalState::HANDSHAKE_VERIFICATION_IN_PROGRESS:
+ return State::VERIFICATION_IN_PROGRESS;
+ case InternalState::HANDSHAKE_FINISHED:
+ return State::FINISHED;
+ case InternalState::HANDSHAKE_ALREADY_USED:
+ return State::ALREADY_USED;
+ case InternalState::HANDSHAKE_ERROR:
+ return State::ERROR;
+ default:
+ // Unreachable.
+ return State::ERROR;
+ }
+}
+
+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
new file mode 100644
index 0000000..99a35a8
--- /dev/null
+++ b/src/main/cpp/src/securegcm/ukey2_shell.cc
@@ -0,0 +1,297 @@
+// 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
new file mode 100644
index 0000000..272f919
--- /dev/null
+++ b/src/main/cpp/test/securegcm/CMakeLists.txt
@@ -0,0 +1,31 @@
+# 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
new file mode 100644
index 0000000..daf69d1
--- /dev/null
+++ b/src/main/cpp/test/securegcm/d2d_connection_context_v1_test.cc
@@ -0,0 +1,124 @@
+// 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
new file mode 100644
index 0000000..5acbb89
--- /dev/null
+++ b/src/main/cpp/test/securegcm/d2d_crypto_ops_test.cc
@@ -0,0 +1,158 @@
+// 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
new file mode 100644
index 0000000..20928fa
--- /dev/null
+++ b/src/main/cpp/test/securegcm/java_util_test.cc
@@ -0,0 +1,84 @@
+// 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/proto/CMakeLists.txt b/src/main/proto/CMakeLists.txt
new file mode 100644
index 0000000..cd94f3f
--- /dev/null
+++ b/src/main/proto/CMakeLists.txt
@@ -0,0 +1,32 @@
+# 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/securegcm.proto b/src/main/proto/securegcm.proto
index 8b45755..0325f06 100644
--- a/src/main/proto/securegcm.proto
+++ b/src/main/proto/securegcm.proto
@@ -1,3 +1,17 @@
+// 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;
diff --git a/third_party/absl b/third_party/absl
new file mode 160000
+Subproject 62f05b1f57ad660e9c09e02ce7d591dcc4d0ca0
diff --git a/third_party/gtest b/third_party/gtest
new file mode 160000
+Subproject 703bd9caab50b139428cea1aaff9974ebee5742
diff --git a/third_party/protobuf b/third_party/protobuf
new file mode 160000
+Subproject d0bfd5221182da1a7cc280f3337b5e41a89539c
diff --git a/third_party/secure_message b/third_party/secure_message
new file mode 160000
+Subproject e7b6988454bc94601616fbbf0db3559f73a1ebd