summaryrefslogtreecommitdiff
path: root/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'src/main')
-rw-r--r--src/main/CMakeLists.txt18
-rw-r--r--src/main/cpp/CMakeLists.txt17
-rw-r--r--src/main/cpp/include/securegcm/d2d_connection_context_v1.h89
-rw-r--r--src/main/cpp/include/securegcm/d2d_crypto_ops.h78
-rw-r--r--src/main/cpp/include/securegcm/java_util.h57
-rw-r--r--src/main/cpp/include/securegcm/ukey2_handshake.h263
-rw-r--r--src/main/cpp/src/securegcm/CMakeLists.txt47
-rw-r--r--src/main/cpp/src/securegcm/d2d_connection_context_v1.cc228
-rw-r--r--src/main/cpp/src/securegcm/d2d_crypto_ops.cc151
-rw-r--r--src/main/cpp/src/securegcm/java_util.cc60
-rw-r--r--src/main/cpp/src/securegcm/ukey2_handshake.cc715
-rw-r--r--src/main/cpp/src/securegcm/ukey2_shell.cc297
-rw-r--r--src/main/cpp/test/securegcm/CMakeLists.txt31
-rw-r--r--src/main/cpp/test/securegcm/d2d_connection_context_v1_test.cc124
-rw-r--r--src/main/cpp/test/securegcm/d2d_crypto_ops_test.cc158
-rw-r--r--src/main/cpp/test/securegcm/java_util_test.cc84
-rw-r--r--src/main/java/com/google/security/annotations/CryptoAnnotation.java68
-rw-r--r--src/main/java/com/google/security/annotations/SuppressInsecureCipherModeCheckerNoReview.java57
-rw-r--r--src/main/java/com/google/security/annotations/SuppressInsecureCipherModeCheckerPendingReview.java59
-rw-r--r--src/main/java/com/google/security/annotations/SuppressInsecureCipherModeCheckerReviewed.java58
-rw-r--r--src/main/java/com/google/security/cryptauth/lib/securegcm/D2DConnectionContext.java274
-rw-r--r--src/main/java/com/google/security/cryptauth/lib/securegcm/D2DConnectionContextV0.java118
-rw-r--r--src/main/java/com/google/security/cryptauth/lib/securegcm/D2DConnectionContextV1.java146
-rw-r--r--src/main/java/com/google/security/cryptauth/lib/securegcm/D2DCryptoOps.java239
-rw-r--r--src/main/java/com/google/security/cryptauth/lib/securegcm/D2DDiffieHellmanKeyExchangeHandshake.java307
-rw-r--r--src/main/java/com/google/security/cryptauth/lib/securegcm/D2DHandshakeContext.java158
-rw-r--r--src/main/java/com/google/security/cryptauth/lib/securegcm/Ed25519.java270
-rw-r--r--src/main/java/com/google/security/cryptauth/lib/securegcm/EnrollmentCryptoOps.java233
-rw-r--r--src/main/java/com/google/security/cryptauth/lib/securegcm/HandshakeException.java32
-rw-r--r--src/main/java/com/google/security/cryptauth/lib/securegcm/KeyEncoding.java180
-rw-r--r--src/main/java/com/google/security/cryptauth/lib/securegcm/SecureGcmConstants.java49
-rw-r--r--src/main/java/com/google/security/cryptauth/lib/securegcm/TransportCryptoOps.java268
-rw-r--r--src/main/java/com/google/security/cryptauth/lib/securegcm/Ukey2Handshake.java1041
-rw-r--r--src/main/java/com/google/security/cryptauth/lib/securegcm/device_to_device_messages_config.asciipb3
-rw-r--r--src/main/java/com/google/security/cryptauth/lib/securegcm/securegcm_config.asciipb4
-rw-r--r--src/main/java/com/google/security/cryptauth/lib/securemessage/CryptoOps.java564
-rw-r--r--src/main/java/com/google/security/cryptauth/lib/securemessage/PublicKeyProtoUtil.java675
-rw-r--r--src/main/java/com/google/security/cryptauth/lib/securemessage/SecureMessageBuilder.java277
-rw-r--r--src/main/java/com/google/security/cryptauth/lib/securemessage/SecureMessageParser.java270
-rw-r--r--src/main/javatest/com/google/security/cryptauth/lib/securegcm/D2DConnectionContextTest.java568
-rw-r--r--src/main/javatest/com/google/security/cryptauth/lib/securegcm/D2DDiffieHellmanKeyExchangeHandshakeTest.java432
-rw-r--r--src/main/javatest/com/google/security/cryptauth/lib/securegcm/Ed25519Test.java195
-rw-r--r--src/main/javatest/com/google/security/cryptauth/lib/securegcm/EnrollmentCryptoOpsTest.java134
-rw-r--r--src/main/javatest/com/google/security/cryptauth/lib/securegcm/KeyEncodingTest.java189
-rw-r--r--src/main/javatest/com/google/security/cryptauth/lib/securegcm/TransportCryptoOpsTest.java110
-rw-r--r--src/main/javatest/com/google/security/cryptauth/lib/securegcm/Ukey2CppCompatibilityTest.java124
-rw-r--r--src/main/javatest/com/google/security/cryptauth/lib/securegcm/Ukey2HandshakeTest.java818
-rw-r--r--src/main/javatest/com/google/security/cryptauth/lib/securegcm/Ukey2ShellCppWrapper.java342
-rw-r--r--src/main/javatest/com/google/security/cryptauth/lib/securemessage/CryptoOpsTest.java172
-rw-r--r--src/main/javatest/com/google/security/cryptauth/lib/securemessage/NullsGoogle3Test.java42
-rw-r--r--src/main/javatest/com/google/security/cryptauth/lib/securemessage/PublicKeyProtoUtilTest.java412
-rw-r--r--src/main/javatest/com/google/security/cryptauth/lib/securemessage/SecureMessageSimpleTestVectorTest.java403
-rw-r--r--src/main/javatest/com/google/security/cryptauth/lib/securemessage/SecureMessageTest.java766
-rw-r--r--src/main/proto/CMakeLists.txt32
-rw-r--r--src/main/proto/device_to_device_messages.proto81
-rw-r--r--src/main/proto/passwordless_auth_payloads.proto37
-rw-r--r--src/main/proto/proximity_payloads.proto59
-rw-r--r--src/main/proto/securegcm.proto308
-rw-r--r--src/main/proto/securemessage.proto126
-rw-r--r--src/main/proto/ukey.proto105
60 files changed, 0 insertions, 13222 deletions
diff --git a/src/main/CMakeLists.txt b/src/main/CMakeLists.txt
deleted file mode 100644
index 776826c..0000000
--- a/src/main/CMakeLists.txt
+++ /dev/null
@@ -1,18 +0,0 @@
-# Copyright 2020 Google LLC
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# https://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-set(UKEY_SRC_ROOT ${CMAKE_CURRENT_LIST_DIR})
-set(UKEY_BINARY_ROOT ${CMAKE_CURRENT_BINARY_DIR})
-add_subdirectory(cpp)
-add_subdirectory(proto)
diff --git a/src/main/cpp/CMakeLists.txt b/src/main/cpp/CMakeLists.txt
deleted file mode 100644
index 919e096..0000000
--- a/src/main/cpp/CMakeLists.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-# Copyright 2020 Google LLC
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# https://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)
-add_subdirectory(src/securegcm)
-add_subdirectory(test/securegcm)
diff --git a/src/main/cpp/include/securegcm/d2d_connection_context_v1.h b/src/main/cpp/include/securegcm/d2d_connection_context_v1.h
deleted file mode 100644
index 098e654..0000000
--- a/src/main/cpp/include/securegcm/d2d_connection_context_v1.h
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SECURITY_CRYPTAUTH_LIB_SECUREGCM_D2D_CONNECTION_CONTEXT_V1_H_
-#define SECURITY_CRYPTAUTH_LIB_SECUREGCM_D2D_CONNECTION_CONTEXT_V1_H_
-
-#include <memory>
-#include <string>
-
-#include "securemessage/crypto_ops.h"
-
-namespace securegcm {
-
-// The full context of a secure connection. This class has methods to encode and
-// decode messages that are to be sent to another device.
-//
-// This class should be kept compatible with the Java implementation in
-// java/com/google/security/cryptauth/lib/securegcm/D2DConnectionContextV1.java
-class D2DConnectionContextV1 {
- public:
- D2DConnectionContextV1(const securemessage::CryptoOps::SecretKey& encode_key,
- const securemessage::CryptoOps::SecretKey& decode_key,
- uint32_t encode_sequence_number,
- uint32_t decode_sequence_number);
-
- // Once the initiator and responder have negotiated a secret key, use this
- // method to encrypt and sign |payload|. Both initiator and responder devices
- // can use this message.
- //
- // On failure, nullptr is returned.
- std::unique_ptr<string> EncodeMessageToPeer(const string& payload);
-
- // Once the initiator and responder have negotiated a secret key, use this
- // method to decrypt and verify a |message| received from the other device.
- // Both initiator and responder devices can use this message.
- //
- // On failure, nullptr is returned.
- std::unique_ptr<string> DecodeMessageFromPeer(const string& message);
-
- // Returns a cryptographic digest (SHA256) of the session keys prepended by
- // the SHA256 hash of the ASCII string "D2D".
- //
- // On failure, nullptr is returned.
- std::unique_ptr<string> GetSessionUnique();
-
- // Creates a saved session that can be later used for resumption. Note,
- // this must be stored in a secure location.
- std::unique_ptr<string> SaveSession();
-
- // Parse a saved session info and attempt to construct a resumed context.
- //
- // The session info passed to this method should be one that was generated
- // by |SaveSession|.
- //
- // On failure, nullptr is returned.
- static std::unique_ptr<D2DConnectionContextV1> FromSavedSession(
- const string& savedSessionInfo);
-
- private:
- // The key used to encode payloads.
- const securemessage::CryptoOps::SecretKey encode_key_;
-
- // The key used to decode received messages.
- const securemessage::CryptoOps::SecretKey decode_key_;
-
- // The current sequence number for encoding.
- uint32_t encode_sequence_number_;
-
- // The current sequence number for decoding.
- uint32_t decode_sequence_number_;
-
- // A friend to access private variables for testing.
- friend class D2DConnectionContextV1Peer;
-};
-
-} // namespace securegcm
-
-#endif // SECURITY_CRYPTAUTH_LIB_SECUREGCM_D2D_CONNECTION_CONTEXT_V1_H_
diff --git a/src/main/cpp/include/securegcm/d2d_crypto_ops.h b/src/main/cpp/include/securegcm/d2d_crypto_ops.h
deleted file mode 100644
index eeeeb20..0000000
--- a/src/main/cpp/include/securegcm/d2d_crypto_ops.h
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SECURITY_CRYPTAUTH_LIB_SECUREGCM_D2D_CRYPTO_OPS_H_
-#define SECURITY_CRYPTAUTH_LIB_SECUREGCM_D2D_CRYPTO_OPS_H_
-
-#include <memory>
-#include <string>
-
-#include "proto/securegcm.pb.h"
-#include "securemessage/crypto_ops.h"
-
-namespace securegcm {
-
-// A collection of static utility methods for the Device to Device communication
-// (D2D) library.
-//
-// A class is used here in preference to a namespace to provide a closer
-// correspondence with the Java equivalent class:
-// //java/com/google/security/cryptauth/lib/securegcm/D2DCryptoOps.java
-class D2DCryptoOps {
- public:
- // Encapsulates a payload type specifier, and a corresponding message as the
- // raw payload.
- //
- // Note: Type is defined in securegcm.proto.
- class Payload {
- public:
- Payload(Type type, const std::string& message);
-
- Type type() const { return type_; }
-
- const std::string& message() const { return message_; }
-
- private:
- const Type type_;
- const std::string message_;
- };
-
- // The salt, SHA256 of "D2D".
- static const uint8_t kSalt[];
- static const size_t kSaltLength;
-
- // Used by a device to send a secure |Payload| to another device.
- static std::unique_ptr<std::string> SigncryptPayload(
- const Payload& payload,
- const securemessage::CryptoOps::SecretKey& secret_key);
-
- // Used by a device to recover a secure |Payload| sent by another device.
- static std::unique_ptr<Payload> VerifyDecryptPayload(
- const std::string& signcrypted_message,
- const securemessage::CryptoOps::SecretKey& secret_key);
-
- // Used to derive a distinct key for each initiator and responder from the
- // |master_key|. Use a different |purpose| for each role.
- static std::unique_ptr<securemessage::CryptoOps::SecretKey>
- DeriveNewKeyForPurpose(const securemessage::CryptoOps::SecretKey& master_key,
- const std::string& purpose);
-
- private:
- // Prevent instantiation.
- D2DCryptoOps();
-};
-
-} // namespace securegcm
-
-#endif // SECURITY_CRYPTAUTH_LIB_SECUREGCM_D2D_CRYPTO_OPS_H_
diff --git a/src/main/cpp/include/securegcm/java_util.h b/src/main/cpp/include/securegcm/java_util.h
deleted file mode 100644
index 8783af4..0000000
--- a/src/main/cpp/include/securegcm/java_util.h
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Utility functions for Java-compatible operations.
-#ifndef SECURITY_CRYPTAUTH_LIB_SECUREGCM_JAVA_UTIL_H_
-#define SECURITY_CRYPTAUTH_LIB_SECUREGCM_JAVA_UTIL_H_
-
-#include "securemessage/byte_buffer.h"
-
-namespace securegcm {
-namespace java_util {
-
-// Perform multiplication with Java overflow semantics
-// (https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html):
-// If an integer multiplication overflows, then the result is the low-order
-// bits of the mathematical product as represented in some sufficiently
-// large two's-complement format.
-int32_t JavaMultiply(int32_t lhs, int32_t rhs);
-
-// Perform addition with Java overflow semantics:
-// (https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html):
-// If an integer addition overflows, then the result is the low-order bits of
-// the mathematical sum as represented in some sufficiently large
-// two's-complement format.
-int32_t JavaAdd(int32_t lhs, int32_t rhs);
-
-// To be compatible with the Java implementation, we need to use the same
-// algorithm as the Arrays#hashCode(byte[]) function in Java:
-// "The value returned by this method is the same value that would be obtained
-// by invoking the hashCode method on a List containing a sequence of Byte
-// instances representing the elements of a in the same order."
-//
-// According to List#hashCode(), this algorithm is:
-// int hashCode = 1;
-// for (Byte b : list) {
-// hashCode = 31 * hashCode + (b == null ? b : b.hashCode());
-// }
-//
-// Finally, Byte#hashCode() is defined as "equal to the result of invoking
-// Byte#intValue()".
-int32_t JavaHashCode(const securemessage::ByteBuffer& byte_buffer);
-
-} // namespace java_util
-} // namespace securegcm
-
-#endif // SECURITY_CRYPTAUTH_LIB_SECUREGCM_JAVA_UTIL_H_
diff --git a/src/main/cpp/include/securegcm/ukey2_handshake.h b/src/main/cpp/include/securegcm/ukey2_handshake.h
deleted file mode 100644
index 8455fd7..0000000
--- a/src/main/cpp/include/securegcm/ukey2_handshake.h
+++ /dev/null
@@ -1,263 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SECURITY_CRYPTAUTH_LIB_SECUREGCM_UKEY2_HANDSHAKE_H_
-#define SECURITY_CRYPTAUTH_LIB_SECUREGCM_UKEY2_HANDSHAKE_H_
-
-#include <map>
-#include <memory>
-
-#include "proto/ukey.pb.h"
-#include "securegcm/d2d_connection_context_v1.h"
-#include "securemessage/crypto_ops.h"
-
-namespace securegcm {
-
-// Implements UKEY2 and produces a |D2DConnectionContextV1|.
-// This class should be kept compatible with the Java implementation in
-// //java/com/google/security/cryptauth/lib/securegcm/Ukey2Handshake.java
-//
-// For usage examples, see ukey2_shell.cc. This file contains a shell exercising
-// both the initiator and responder handshake roles.
-class UKey2Handshake {
- public:
- // Handshake states:
- // kInProgress:
- // The handshake is in progress, caller should use
- // |GetNextHandshakeMessage()| and |ParseHandshakeMessage()| to continue
- // the handshake.
- //
- // kVerificationNeeded:
- // The handshake is complete, but pending verification of the
- // authentication string. Clients should use |GetVerificationString()|
- // to get the verification string and use out-of-band methods to
- // authenticate the handshake.
- //
- // kVerificationInProgress:
- // The handshake is complete, verification string has been generated,
- // but has not been confirmed. After authenticating the handshake
- // out-of-band, use |VerifyHandshake()| to mark the handshake as
- // verified.
- //
- // kFinished:
- // The handshake is finished, and the caller can use
- // |ToConnectionContext()| to produce a |D2DConnectionContextV1|.
- //
- // kAlreadyUsed:
- // The hanshake has already been used and should be destroyed.
- //
- // kError:
- // The handshake produced an error and should be destroyed.
- enum class State {
- kInProgress,
- kVerificationNeeded,
- kVerificationInProgress,
- kFinished,
- kAlreadyUsed,
- kError,
- };
-
- // Currently implemented UKEY2 handshake ciphers. Each cipher is a tuple
- // consisting of a key negotiation cipher and a hash function used for a
- // commitment. Currently the ciphers are:
- // +-----------------------------------------------------+
- // | Enum | Key negotiation | Hash function |
- // +-------------+-----------------------+---------------+
- // | P256_SHA512 | ECDH using NIST P-256 | SHA512 |
- // +-----------------------------------------------------+
- //
- // Note that these should correspond to values in
- // device_to_device_messages.proto.
- enum class HandshakeCipher : int {
- // TODO(aczeskis): add CURVE25519_SHA512
-
- P256_SHA512 = securegcm::P256_SHA512,
- };
-
- // Creates a |UKey2Handshake| with a particular |cipher| that can be used by
- // an initiator / client.
- static std::unique_ptr<UKey2Handshake> ForInitiator(HandshakeCipher cipher);
-
- // Creates a |UKey2Handshake| with a particular |cipher| that can be used by
- // a responder / server.
- static std::unique_ptr<UKey2Handshake> ForResponder(HandshakeCipher cipher);
-
- // Returns the current state of the handshake.
- State GetHandshakeState() const;
-
- // Returns the last error message. Empty string if there was no error.
- const string& GetLastError() const;
-
- // Gets the next handshake message suitable for sending on the wire.
- // If |nullptr| is returned, check |GetLastError()| for the error message.
- std::unique_ptr<string> GetNextHandshakeMessage();
-
- // Parses the given |handshake_message|, updating the internal state.
- struct ParseResult {
- // True if |handshake_message| is parsed successfully. If |false|, call
- // |GetLastError()| for the error message.
- bool success;
-
- // May be set if parsing fails. This value should be sent to the remote
- // device before disconnecting.
- std::unique_ptr<string> alert_to_send;
- };
- ParseResult ParseHandshakeMessage(const string& handshake_message);
-
- // Returns an authentication string suitable for authenticating the handshake
- // out-of-band. Note that the authentication string can be short (e.g., a 6
- // digit visual confirmation code).
- //
- // Note: This should only be called when the state returned from
- // |GetHandshakeState()| is |State::VERIFICATION_NEEDED|, which means this can
- // only be called once.
- //
- // |byte_length|: The length of the output. Min length is 1; max length is 32.
- // If |nullptr| is returned, check |GetLastError()| for the error message.
- std::unique_ptr<string> GetVerificationString(int byte_length);
-
- // Invoked to let the handshake state machine know that caller has validated
- // the authentication string obtained via |GetVerificationString()|.
- // Note: This should only be called when the state returned by
- // |GetHandshakeState()| is |State::VERIFICATION_IN_PROGRESS|.
- //
- // If |false| is returned, check |GetLastError()| for the error message.
- bool VerifyHandshake();
-
- // Can be called to generate a |D2DConnectionContextV1|. Returns nullptr on
- // failure.
- // Note: This should only be called when the state returned by
- // |GetHandshakeState()| is |State::FINISHED|.
- //
- // If |nullptr| is returned, check |GetLastError()| for the error message.
- std::unique_ptr<D2DConnectionContextV1> ToConnectionContext();
-
- private:
- // Enums for internal state machinery.
- enum class InternalState : int {
- CLIENT_START,
- CLIENT_WAITING_FOR_SERVER_INIT,
- CLIENT_AFTER_SERVER_INIT,
-
- // Responder/server state
- SERVER_START,
- SERVER_AFTER_CLIENT_INIT,
- SERVER_WAITING_FOR_CLIENT_FINISHED,
-
- // Common completion state
- HANDSHAKE_VERIFICATION_NEEDED,
- HANDSHAKE_VERIFICATION_IN_PROGRESS,
- HANDSHAKE_FINISHED,
- HANDSHAKE_ALREADY_USED,
- HANDSHAKE_ERROR,
- };
-
- // Helps us remember our role in the handshake.
- enum class HandshakeRole {
- CLIENT,
- SERVER
- };
-
- // Prevent public instantiation. Callers should use |ForInitiator()| or
- // |ForResponder()|.
- UKey2Handshake(InternalState state, HandshakeCipher cipher);
-
- // Attempts to parse Ukey2ClientInit, wrapped inside a Ukey2Message.
- // See go/ukey2 for details.
- ParseResult ParseClientInitUkey2Message(const string& handshake_message);
-
- // Attempts to parse Ukey2ServerInit, wrapped inside a Ukey2Message.
- // See go/ukey2 for details.
- ParseResult ParseServerInitUkey2Message(const string& handshake_message);
-
- // Attempts to parse Ukey2ClientFinish, wrapped inside a Ukey2Message.
- // See go/ukey2 for details.
- ParseResult ParseClientFinishUkey2Message(const string& handshake_message);
-
- // Convenience function to set |last_error_| and create a ParseResult with a
- // given alert.
- ParseResult CreateFailedResultWithAlert(Ukey2Alert::AlertType alert_type,
- const string& error_message);
-
- // Convenience function to set |last_error_| and create a failed ParseResult
- // without an alert.
- ParseResult CreateFailedResultWithoutAlert(const string& error_message);
-
- // Convenience function to create a successful ParseResult.
- ParseResult CreateSuccessResult();
-
- // Verifies that the peer's commitment stored in |peer_commitment_| is the
- // same as that obtained from |handshake_message|.
- bool VerifyCommitment(const string& handshake_message);
-
- // Generates a commitment for the P256_SHA512 cipher.
- std::unique_ptr<Ukey2ClientInit::CipherCommitment>
- GenerateP256Sha512Commitment();
-
- // Creates a serialized Ukey2Message, wrapping an inner ClientInit message.
- std::unique_ptr<string> MakeClientInitUkey2Message();
-
- // Creates a serialized Ukey2Message, wrapping an inner ServerInit message.
- std::unique_ptr<string> MakeServerInitUkey2Message();
-
- // Creates a serialized Ukey2Message of a given |type|, wrapping |data|.
- std::unique_ptr<string> MakeUkey2Message(Ukey2Message::Type type,
- const string& data);
-
- // Called when an error occurs to set |handshake_state_| and |last_error_|.
- void SetError(const string& error_message);
-
- // The current state of the handshake.
- InternalState handshake_state_;
-
- // The cipher to use for the handshake.
- const HandshakeCipher handshake_cipher_;
-
- // The role to perform, i.e. client or server.
- const HandshakeRole handshake_role_;
-
- // A newly generated key-pair for this handshake.
- std::unique_ptr<securemessage::CryptoOps::KeyPair> our_key_pair_;
-
- // The peer's public key retrieved from a handshake message.
- std::unique_ptr<securemessage::CryptoOps::PublicKey> their_public_key_;
-
- // The secret key derived from |our_key_pair_| and |their_public_key_|.
- std::unique_ptr<securemessage::CryptoOps::SecretKey> derived_secret_key_;
-
- // The raw bytes of the Ukey2ClientInit, wrapped inside a Ukey2Message.
- // Empty string if not initialized.
- string wrapped_client_init_;
-
- // The raw bytes of the Ukey2ServerInit, wrapped inside a Ukey2Message.
- // Empty string if not initialized.
- string wrapped_server_init_;
-
- // The commitment of the peer retrieved from a handshake message. Empty string
- // if not initialized.
- string peer_commitment_;
-
- // Map from ciphers to the raw bytes of message 3 (which is a wrapped
- // Ukey2ClientFinished message).
- // Note: Currently only one cipher is supported, so at most one entry exists
- // in this map.
- std::map<HandshakeCipher, string> raw_message3_map_;
-
- // Contains the last error message.
- string last_error_;
-};
-
-} // namespace securegcm
-
-#endif // SECURITY_CRYPTAUTH_LIB_SECUREGCM_UKEY2_HANDSHAKE_H_
diff --git a/src/main/cpp/src/securegcm/CMakeLists.txt b/src/main/cpp/src/securegcm/CMakeLists.txt
deleted file mode 100644
index b562756..0000000
--- a/src/main/cpp/src/securegcm/CMakeLists.txt
+++ /dev/null
@@ -1,47 +0,0 @@
-# Copyright 2020 Google LLC
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# https://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-add_library(ukey2 STATIC
- d2d_connection_context_v1.cc
- d2d_crypto_ops.cc
- java_util.cc
- ukey2_handshake.cc
-)
-
-target_include_directories(ukey2
- PUBLIC
- ${PROJECT_SOURCE_DIR}/src/main/cpp/include
-)
-
-target_link_libraries(ukey2
- PUBLIC
- proto_device_to_device_messages_cc_proto
- proto_securegcm_cc_proto
- proto_ukey_cc_proto
- securemessage
-)
-
-add_executable(ukey2_shell
- ukey2_shell.cc
-)
-
-target_link_libraries(ukey2_shell
- PUBLIC
- securemessage
- ukey2
- absl::base
- absl::container
- absl::flags
- absl::flags_parse
-)
diff --git a/src/main/cpp/src/securegcm/d2d_connection_context_v1.cc b/src/main/cpp/src/securegcm/d2d_connection_context_v1.cc
deleted file mode 100644
index 8a9a612..0000000
--- a/src/main/cpp/src/securegcm/d2d_connection_context_v1.cc
+++ /dev/null
@@ -1,228 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "securegcm/d2d_connection_context_v1.h"
-
-#include <limits>
-#include <sstream>
-
-#include "proto/device_to_device_messages.pb.h"
-#include "proto/securegcm.pb.h"
-#include "securegcm/d2d_crypto_ops.h"
-#include "securegcm/java_util.h"
-#include "securemessage/secure_message_builder.h"
-#include "securemessage/util.h"
-
-namespace securegcm {
-
-using securemessage::CryptoOps;
-using securemessage::ByteBuffer;
-using securemessage::Util;
-
-namespace {
-
-// Fields to fill in the GcmMetadata proto.
-const Type kGcmMetadataType = DEVICE_TO_DEVICE_MESSAGE;
-
-// Represents the version of this context.
-const uint8_t kProtocolVersion = 1;
-
-// The following represent the starting positions of the each entry within
-// the string representation of this D2DConnectionContextV1.
-//
-// The saved session has a 1 byte protocol version, two 4 byte sequence numbers,
-// and two 32 byte AES keys: (1 + 4 + 4 + 32 + 32 = 73).
-
-// The two sequence numbers are 4 bytes each.
-const int kSequenceNumberLength = 4;
-
-// 32 byte AES keys.
-const int kAesKeyLength = 32;
-
-// The encode sequence number starts at 1 to account for the 1 byte version
-// number.
-const int kEncodeSequenceStart = 1;
-const int kEncodeSequenceEnd = kEncodeSequenceStart + kSequenceNumberLength;
-
-const int kDecodeSequenceStart = kEncodeSequenceEnd;
-const int kDecodeSequenceEnd = kDecodeSequenceStart + kSequenceNumberLength;
-
-const int kEncodeKeyStart = kDecodeSequenceEnd;
-const int kEncodeKeyEnd = kEncodeKeyStart + kAesKeyLength;
-
-const int kDecodeKeyStart = kEncodeKeyEnd;
-const int kSavedSessionLength = kDecodeKeyStart + kAesKeyLength;
-
-// Convenience function to creates a DeviceToDeviceMessage proto with |payload|
-// and |sequence_number|.
-DeviceToDeviceMessage CreateDeviceToDeviceMessage(const std::string& payload,
- uint32_t sequence_number) {
- DeviceToDeviceMessage device_to_device_message;
- device_to_device_message.set_sequence_number(sequence_number);
- device_to_device_message.set_message(payload);
- return device_to_device_message;
-}
-
-// Convert 4 bytes in big-endian representation into an unsigned int.
-uint32_t BytesToUnsignedInt(std::vector<uint8_t> bytes) {
- return bytes[0] << 24 | bytes[1] << 12 | bytes[2] << 8 | bytes[3];
-}
-
-// Convert an unsigned int into a 4 byte big-endian representation.
-std::vector<uint8_t> UnsignedIntToBytes(uint32_t val) {
- return {static_cast<uint8_t>(val >> 24), static_cast<uint8_t>(val >> 12),
- static_cast<uint8_t>(val >> 8), static_cast<uint8_t>(val)};
-}
-
-} // namespace
-
-D2DConnectionContextV1::D2DConnectionContextV1(
- const CryptoOps::SecretKey& encode_key,
- const CryptoOps::SecretKey& decode_key, uint32_t encode_sequence_number,
- uint32_t decode_sequence_number)
- : encode_key_(encode_key),
- decode_key_(decode_key),
- encode_sequence_number_(encode_sequence_number),
- decode_sequence_number_(decode_sequence_number) {}
-
-std::unique_ptr<std::string> D2DConnectionContextV1::EncodeMessageToPeer(
- const std::string& payload) {
- encode_sequence_number_++;
- const DeviceToDeviceMessage message =
- CreateDeviceToDeviceMessage(payload, encode_sequence_number_);
-
- const D2DCryptoOps::Payload payload_with_type(kGcmMetadataType,
- message.SerializeAsString());
- return D2DCryptoOps::SigncryptPayload(payload_with_type, encode_key_);
-}
-
-std::unique_ptr<std::string> D2DConnectionContextV1::DecodeMessageFromPeer(
- const std::string& message) {
- std::unique_ptr<D2DCryptoOps::Payload> payload =
- D2DCryptoOps::VerifyDecryptPayload(message, decode_key_);
- if (!payload) {
- Util::LogError("DecodeMessageFromPeer: Failed to verify message.");
- return nullptr;
- }
-
- if (kGcmMetadataType != payload->type()) {
- Util::LogError("DecodeMessageFromPeer: Wrong message type in D2D message.");
- return nullptr;
- }
-
- DeviceToDeviceMessage d2d_message;
- if (!d2d_message.ParseFromString(payload->message())) {
- Util::LogError("DecodeMessageFromPeer: Unable to parse D2D message proto.");
- return nullptr;
- }
-
- decode_sequence_number_++;
- if (d2d_message.sequence_number() != decode_sequence_number_) {
- std::ostringstream stream;
- stream << "DecodeMessageFromPeer: Seqno in D2D message ("
- << d2d_message.sequence_number()
- << ") does not match expected seqno (" << decode_sequence_number_
- << ").";
- Util::LogError(stream.str());
- return nullptr;
- }
-
- return std::unique_ptr<std::string>(d2d_message.release_message());
-}
-
-std::unique_ptr<std::string> D2DConnectionContextV1::GetSessionUnique() {
- const ByteBuffer encode_key_data = encode_key_.data();
- const ByteBuffer decode_key_data = decode_key_.data();
- const int32_t encode_key_hash = java_util::JavaHashCode(encode_key_data);
- const int32_t decode_key_hash = java_util::JavaHashCode(decode_key_data);
-
- const ByteBuffer& first_buffer =
- encode_key_hash < decode_key_hash ? encode_key_data : decode_key_data;
- const ByteBuffer& second_buffer =
- encode_key_hash < decode_key_hash ? decode_key_data : encode_key_data;
-
- ByteBuffer data_to_hash(D2DCryptoOps::kSalt, D2DCryptoOps::kSaltLength);
- data_to_hash = ByteBuffer::Concat(data_to_hash, first_buffer);
- data_to_hash = ByteBuffer::Concat(data_to_hash, second_buffer);
-
- std::unique_ptr<ByteBuffer> hash = CryptoOps::Sha256(data_to_hash);
- if (!hash) {
- Util::LogError("GetSessionUnique: SHA-256 hash failed.");
- return nullptr;
- }
-
- return std::unique_ptr<std::string>(new std::string(hash->String()));
-}
-
-// Structure of saved session is:
-//
-// +---------------------------------------------------------------------------+
-// | 1 Byte | 4 Bytes | 4 Bytes | 32 Bytes | 32 Bytes |
-// +---------------------------------------------------------------------------+
-// | Version | encode seq number | decode seq number | encode key | decode key |
-// +---------------------------------------------------------------------------+
-//
-// The sequence numbers are represented in big-endian.
-std::unique_ptr<std::string> D2DConnectionContextV1::SaveSession() {
- ByteBuffer byteBuffer = ByteBuffer(&kProtocolVersion, static_cast<size_t>(1));
-
- // Append encode sequence number.
- std::vector<uint8_t> encode_sequence_number_bytes =
- UnsignedIntToBytes(encode_sequence_number_);
- for (int i = 0; i < encode_sequence_number_bytes.size(); i++) {
- byteBuffer.Append(static_cast<size_t>(1), encode_sequence_number_bytes[i]);
- }
-
- // Append decode sequence number.
- std::vector<uint8_t> decode_sequence_number_bytes =
- UnsignedIntToBytes(decode_sequence_number_);
- for (int i = 0; i < decode_sequence_number_bytes.size(); i++) {
- byteBuffer.Append(static_cast<size_t>(1), decode_sequence_number_bytes[i]);
- }
-
- // Append encode key.
- byteBuffer = ByteBuffer::Concat(byteBuffer, encode_key_.data());
-
- // Append decode key.
- byteBuffer = ByteBuffer::Concat(byteBuffer, decode_key_.data());
-
- return std::unique_ptr<std::string>(new std::string(byteBuffer.String()));
-}
-
-// static.
-std::unique_ptr<D2DConnectionContextV1>
-D2DConnectionContextV1::FromSavedSession(const std::string& savedSessionInfo) {
- ByteBuffer byteBuffer = ByteBuffer(savedSessionInfo);
-
- if (byteBuffer.size() != kSavedSessionLength) {
- return nullptr;
- }
-
- uint32_t encode_sequence_number = BytesToUnsignedInt(
- byteBuffer.SubArray(kEncodeSequenceStart, kEncodeSequenceEnd)->Vector());
- uint32_t decode_sequence_number = BytesToUnsignedInt(
- byteBuffer.SubArray(kDecodeSequenceStart, kDecodeSequenceEnd)->Vector());
-
- const CryptoOps::SecretKey encode_key =
- CryptoOps::SecretKey(*byteBuffer.SubArray(kEncodeKeyStart, kAesKeyLength),
- CryptoOps::KeyAlgorithm::AES_256_KEY);
- const CryptoOps::SecretKey decode_key =
- CryptoOps::SecretKey(*byteBuffer.SubArray(kDecodeKeyStart, kAesKeyLength),
- CryptoOps::KeyAlgorithm::AES_256_KEY);
-
- return std::unique_ptr<D2DConnectionContextV1>(new D2DConnectionContextV1(
- encode_key, decode_key, encode_sequence_number, decode_sequence_number));
-}
-
-} // namespace securegcm
diff --git a/src/main/cpp/src/securegcm/d2d_crypto_ops.cc b/src/main/cpp/src/securegcm/d2d_crypto_ops.cc
deleted file mode 100644
index 49f0b85..0000000
--- a/src/main/cpp/src/securegcm/d2d_crypto_ops.cc
+++ /dev/null
@@ -1,151 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "securegcm/d2d_crypto_ops.h"
-
-#include <sstream>
-
-#include "securemessage/secure_message_builder.h"
-#include "securemessage/secure_message_parser.h"
-#include "securemessage/util.h"
-
-namespace securegcm {
-
-using securemessage::CryptoOps;
-using securemessage::HeaderAndBody;
-using securemessage::SecureMessage;
-using securemessage::SecureMessageBuilder;
-using securemessage::SecureMessageParser;
-using securemessage::Util;
-
-namespace {
-
-// The current protocol version.
-const int kSecureGcmProtocolVersion = 1;
-
-// The number of bytes in an expected AES256 key.
-const int kAes256KeyLength = 32;
-}
-
-// static.
-const uint8_t D2DCryptoOps::kSalt[] = {
- 0x82, 0xAA, 0x55, 0xA0, 0xD3, 0x97, 0xF8, 0x83, 0x46, 0xCA, 0x1C,
- 0xEE, 0x8D, 0x39, 0x09, 0xB9, 0x5F, 0x13, 0xFA, 0x7D, 0xEB, 0x1D,
- 0x4A, 0xB3, 0x83, 0x76, 0xB8, 0x25, 0x6D, 0xA8, 0x55, 0x10};
-
-// static.
-const size_t D2DCryptoOps::kSaltLength = sizeof(D2DCryptoOps::kSalt);
-
-D2DCryptoOps::Payload::Payload(Type type, const string& message)
- : type_(type), message_(message) {}
-
-D2DCryptoOps::D2DCryptoOps() {}
-
-// static.
-std::unique_ptr<string> D2DCryptoOps::SigncryptPayload(
- const Payload& payload, const CryptoOps::SecretKey& secret_key) {
- GcmMetadata gcm_metadata;
- gcm_metadata.set_type(payload.type());
- gcm_metadata.set_version(kSecureGcmProtocolVersion);
-
- SecureMessageBuilder builder;
- builder.SetPublicMetadata(gcm_metadata.SerializeAsString());
-
- std::unique_ptr<SecureMessage> secure_message =
- builder.BuildSignCryptedMessage(secret_key, CryptoOps::HMAC_SHA256,
- secret_key, CryptoOps::AES_256_CBC,
- payload.message());
- if (!secure_message) {
- Util::LogError("Unable to encrypt payload.");
- return nullptr;
- }
-
- return std::unique_ptr<string>(
- new string(secure_message->SerializeAsString()));
-}
-
-// static.
-std::unique_ptr<D2DCryptoOps::Payload> D2DCryptoOps::VerifyDecryptPayload(
- const string& signcrypted_message, const CryptoOps::SecretKey& secret_key) {
- SecureMessage secure_message;
- if (!secure_message.ParseFromString(signcrypted_message)) {
- Util::LogError("VerifyDecryptPayload: error parsing SecureMessage.");
- return nullptr;
- }
-
- std::unique_ptr<HeaderAndBody> header_and_body =
- SecureMessageParser::ParseSignCryptedMessage(
- secure_message, secret_key, CryptoOps::HMAC_SHA256, secret_key,
- CryptoOps::AES_256_CBC, string() /* associated_data */);
- if (!header_and_body) {
- Util::LogError("VerifyDecryptPayload: error verifying SecureMessage.");
- return nullptr;
- }
-
- if (!header_and_body->header().has_public_metadata()) {
- Util::LogError("VerifyDecryptPayload: no public metadata in header.");
- return nullptr;
- }
-
- GcmMetadata metadata;
- if (!metadata.ParseFromString(header_and_body->header().public_metadata())) {
- Util::LogError("VerifyDecryptPayload: Failed to parse GcmMetadata.");
- return nullptr;
- }
-
- if (metadata.version() != kSecureGcmProtocolVersion) {
- std::ostringstream stream;
- stream << "VerifyDecryptPayload: Unsupported protocol version "
- << metadata.version();
- Util::LogError(stream.str());
- return nullptr;
- }
-
- return std::unique_ptr<Payload>(
- new Payload(metadata.type(), header_and_body->body()));
-}
-
-// static.
-std::unique_ptr<CryptoOps::SecretKey> D2DCryptoOps::DeriveNewKeyForPurpose(
- const securemessage::CryptoOps::SecretKey& master_key,
- const string& purpose) {
- if (master_key.data().size() != kAes256KeyLength) {
- Util::LogError("DeriveNewKeyForPurpose: Invalid master_key length.");
- return nullptr;
- }
-
- if (purpose.empty()) {
- Util::LogError("DeriveNewKeyForPurpose: purpose is empty.");
- return nullptr;
- }
-
- std::unique_ptr<string> raw_derived_key = CryptoOps::Hkdf(
- master_key.data().String(),
- string(reinterpret_cast<const char *>(kSalt), kSaltLength),
- purpose);
- if (!raw_derived_key) {
- Util::LogError("DeriveNewKeyForPurpose: hkdf failed.");
- return nullptr;
- }
-
- if (raw_derived_key->size() != kAes256KeyLength) {
- Util::LogError("DeriveNewKeyForPurpose: Unexpected size of derived key.");
- return nullptr;
- }
-
- return std::unique_ptr<CryptoOps::SecretKey>(
- new CryptoOps::SecretKey(*raw_derived_key, CryptoOps::AES_256_KEY));
-}
-
-} // namespace securegcm
diff --git a/src/main/cpp/src/securegcm/java_util.cc b/src/main/cpp/src/securegcm/java_util.cc
deleted file mode 100644
index 1ce4d7b..0000000
--- a/src/main/cpp/src/securegcm/java_util.cc
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "securegcm/java_util.h"
-
-#include <cstring>
-
-namespace securegcm {
-namespace java_util {
-
-namespace {
-
-// Returns the lower 32-bits of a int64_t |value| as an int32_t.
-int32_t Lower32Bits(int64_t value) {
- const uint32_t lower_bits = static_cast<uint32_t>(value & 0xFFFFFFFF);
- int32_t return_value;
- std::memcpy(&return_value, &lower_bits, sizeof(uint32_t));
- return return_value;
-}
-
-} // namespace
-
-int32_t JavaMultiply(int32_t lhs, int32_t rhs) {
- // Multiplication guaranteed to fit in int64_t, range from [2^63, 2^63 - 1].
- // Minimum value is (-2^31)^2 = 2^62.
- const int64_t result = static_cast<int64_t>(lhs) * static_cast<int64_t>(rhs);
- return Lower32Bits(result);
-}
-
-int32_t JavaAdd(int32_t lhs, int32_t rhs) {
- const int64_t result = static_cast<int64_t>(lhs) + static_cast<int64_t>(rhs);
- return Lower32Bits(result);
-}
-
-int32_t JavaHashCode(const securemessage::ByteBuffer& byte_buffer) {
- const string bytes = byte_buffer.String();
- int32_t hash_code = 1;
- for (const int8_t byte : bytes) {
- int32_t int_value = static_cast<int32_t>(byte);
- // Java relies on the overflow/underflow behaviour of arithmetic operations,
- // which is undefined in C++, so we call our own Java-compatible versions of
- // + and * here.
- hash_code = JavaAdd(JavaMultiply(31, hash_code), int_value);
- }
- return hash_code;
-}
-
-} // namespace java_util
-} // namespace securegcm
diff --git a/src/main/cpp/src/securegcm/ukey2_handshake.cc b/src/main/cpp/src/securegcm/ukey2_handshake.cc
deleted file mode 100644
index dc5c131..0000000
--- a/src/main/cpp/src/securegcm/ukey2_handshake.cc
+++ /dev/null
@@ -1,715 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "securegcm/ukey2_handshake.h"
-
-#include <sstream>
-
-#include "securegcm/d2d_crypto_ops.h"
-#include "securemessage/public_key_proto_util.h"
-
-namespace securegcm {
-
-using securemessage::ByteBuffer;
-using securemessage::CryptoOps;
-using securemessage::GenericPublicKey;
-using securemessage::PublicKeyProtoUtil;
-
-namespace {
-
-// Salt value used to derive client and server keys for next protocol.
-const char kUkey2HkdfSalt[] = "UKEY2 v1 next";
-
-// Salt value used to derive verification string.
-const char kUkey2VerificationStringSalt[] = "UKEY2 v1 auth";
-
-// Maximum version of the handshake supported by this class.
-const uint32_t kVersion = 1;
-
-// Random nonce is fixed at 32 bytes (as per go/ukey2).
-const uint32_t kNonceLengthInBytes = 32;
-
-// Currently, we only support one next protocol.
-const char kNextProtocol[] = "AES_256_CBC-HMAC_SHA256";
-
-// Creates the appropriate KeyPair for |cipher|.
-std::unique_ptr<CryptoOps::KeyPair> GenerateKeyPair(
- UKey2Handshake::HandshakeCipher cipher) {
- switch (cipher) {
- case UKey2Handshake::HandshakeCipher::P256_SHA512:
- return CryptoOps::GenerateEcP256KeyPair();
- default:
- return nullptr;
- }
-}
-
-// Parses a CryptoOps::PublicKey from a serialized GenericPublicKey.
-std::unique_ptr<securemessage::CryptoOps::PublicKey> ParsePublicKey(
- const string& serialized_generic_public_key) {
- GenericPublicKey generic_public_key;
- if (!generic_public_key.ParseFromString(serialized_generic_public_key)) {
- return nullptr;
- }
- return PublicKeyProtoUtil::ParsePublicKey(generic_public_key);
-}
-
-} // namespace
-
-// static.
-std::unique_ptr<UKey2Handshake> UKey2Handshake::ForInitiator(
- HandshakeCipher cipher) {
- return std::unique_ptr<UKey2Handshake>(
- new UKey2Handshake(InternalState::CLIENT_START, cipher));
-}
-
-// static.
-std::unique_ptr<UKey2Handshake> UKey2Handshake::ForResponder(
- HandshakeCipher cipher) {
- return std::unique_ptr<UKey2Handshake>(
- new UKey2Handshake(InternalState::SERVER_START, cipher));
-}
-
-UKey2Handshake::UKey2Handshake(InternalState state, HandshakeCipher cipher)
- : handshake_state_(state),
- handshake_cipher_(cipher),
- handshake_role_(state == InternalState::CLIENT_START
- ? HandshakeRole::CLIENT
- : HandshakeRole::SERVER),
- our_key_pair_(GenerateKeyPair(cipher)) {}
-
-UKey2Handshake::State UKey2Handshake::GetHandshakeState() const {
- switch (handshake_state_) {
- case InternalState::CLIENT_START:
- case InternalState::CLIENT_WAITING_FOR_SERVER_INIT:
- case InternalState::CLIENT_AFTER_SERVER_INIT:
- case InternalState::SERVER_START:
- case InternalState::SERVER_AFTER_CLIENT_INIT:
- case InternalState::SERVER_WAITING_FOR_CLIENT_FINISHED:
- // Fallthrough intended -- these are all in-progress states.
- return State::kInProgress;
- case InternalState::HANDSHAKE_VERIFICATION_NEEDED:
- return State::kVerificationNeeded;
- case InternalState::HANDSHAKE_VERIFICATION_IN_PROGRESS:
- return State::kVerificationInProgress;
- case InternalState::HANDSHAKE_FINISHED:
- return State::kFinished;
- case InternalState::HANDSHAKE_ALREADY_USED:
- return State::kAlreadyUsed;
- case InternalState::HANDSHAKE_ERROR:
- return State::kError;
- default:
- // Unreachable.
- return State::kError;
- }
-}
-
-const string& UKey2Handshake::GetLastError() const {
- return last_error_;
-}
-
-std::unique_ptr<string> UKey2Handshake::GetNextHandshakeMessage() {
- switch (handshake_state_) {
- case InternalState::CLIENT_START: {
- std::unique_ptr<string> client_init = MakeClientInitUkey2Message();
- if (!client_init) {
- // |last_error_| is already set.
- return nullptr;
- }
-
- wrapped_client_init_ = *client_init;
- handshake_state_ = InternalState::CLIENT_WAITING_FOR_SERVER_INIT;
- return client_init;
- }
-
- case InternalState::SERVER_AFTER_CLIENT_INIT: {
- std::unique_ptr<string> server_init = MakeServerInitUkey2Message();
- if (!server_init) {
- // |last_error_| is already set.
- return nullptr;
- }
-
- wrapped_server_init_ = *server_init;
- handshake_state_ = InternalState::SERVER_WAITING_FOR_CLIENT_FINISHED;
- return server_init;
- }
-
- case InternalState::CLIENT_AFTER_SERVER_INIT: {
- // Make sure we have a message 3 for the chosen cipher.
- if (raw_message3_map_.count(handshake_cipher_) == 0) {
- std::ostringstream stream;
- stream << "Client state is CLIENT_AFTER_SERVER_INIT, and cipher is "
- << static_cast<int>(handshake_cipher_)
- << ", but no corresponding raw "
- << "[Client Finished] message has been generated.";
- SetError(stream.str());
- return nullptr;
- }
- handshake_state_ = InternalState::HANDSHAKE_VERIFICATION_NEEDED;
- return std::unique_ptr<string>(
- new string(raw_message3_map_[handshake_cipher_]));
- }
-
- default: {
- std::ostringstream stream;
- stream << "Cannot get next message in state "
- << static_cast<int>(handshake_state_);
- SetError(stream.str());
- return nullptr;
- }
- }
-}
-
-UKey2Handshake::ParseResult
-UKey2Handshake::ParseHandshakeMessage(const string& handshake_message) {
- switch (handshake_state_) {
- case InternalState::SERVER_START:
- return ParseClientInitUkey2Message(handshake_message);
- case InternalState::CLIENT_WAITING_FOR_SERVER_INIT:
- return ParseServerInitUkey2Message(handshake_message);
- case InternalState::SERVER_WAITING_FOR_CLIENT_FINISHED:
- return ParseClientFinishUkey2Message(handshake_message);
- default:
- std::ostringstream stream;
- stream << "Cannot parse message in state "
- << static_cast<int>(handshake_state_);
- SetError(stream.str());
- return {false, nullptr};
- }
-}
-
-std::unique_ptr<string> UKey2Handshake::GetVerificationString(int byte_length) {
- if (byte_length < 1 || byte_length > 32) {
- SetError("Minimum length is 1 byte, max is 32 bytes.");
- return nullptr;
- }
-
- if (handshake_state_ != InternalState::HANDSHAKE_VERIFICATION_NEEDED) {
- std::ostringstream stream;
- stream << "Unexpected state: " << static_cast<int>(handshake_state_);
- SetError(stream.str());
- return nullptr;
- }
-
- if (!our_key_pair_ || !our_key_pair_->private_key || !their_public_key_) {
- SetError("One of our private key or their public key is null.");
- return nullptr;
- }
-
- switch (handshake_cipher_) {
- case HandshakeCipher::P256_SHA512:
- derived_secret_key_ = CryptoOps::KeyAgreementSha256(
- *(our_key_pair_->private_key), *their_public_key_);
- break;
- default:
- // Unreachable.
- return nullptr;
- }
-
- if (!derived_secret_key_) {
- SetError("Failed to derive shared secret key.");
- return nullptr;
- }
-
- std::unique_ptr<string> auth_string = CryptoOps::Hkdf(
- derived_secret_key_->data().String(),
- string(kUkey2VerificationStringSalt, sizeof(kUkey2VerificationStringSalt)),
- wrapped_client_init_ + wrapped_server_init_);
-
- handshake_state_ = InternalState::HANDSHAKE_VERIFICATION_IN_PROGRESS;
- return auth_string;
-}
-
-bool UKey2Handshake::VerifyHandshake() {
- if (handshake_state_ != InternalState::HANDSHAKE_VERIFICATION_IN_PROGRESS) {
- std::ostringstream stream;
- stream << "Unexpected state: " << static_cast<int>(handshake_state_);
- SetError(stream.str());
- return false;
- }
-
- handshake_state_ = InternalState::HANDSHAKE_FINISHED;
- return true;
-}
-
-std::unique_ptr<D2DConnectionContextV1> UKey2Handshake::ToConnectionContext() {
- if (InternalState::HANDSHAKE_FINISHED != handshake_state_) {
- std::ostringstream stream;
- stream << "ToConnectionContext can only be called when handshake is "
- << "completed, but current state is "
- << static_cast<int>(handshake_state_);
- SetError(stream.str());
- return nullptr;
- }
-
- if (!derived_secret_key_) {
- SetError("Derived key is null.");
- return nullptr;
- }
-
- string info = wrapped_client_init_ + wrapped_server_init_;
- std::unique_ptr<string> master_key_data = CryptoOps::Hkdf(
- derived_secret_key_->data().String(), kUkey2HkdfSalt, info);
-
- if (!master_key_data) {
- SetError("Failed to create master key.");
- return nullptr;
- }
-
- // Derive separate encode keys for both client and server.
- CryptoOps::SecretKey master_key(*master_key_data, CryptoOps::AES_256_KEY);
- std::unique_ptr<CryptoOps::SecretKey> client_key =
- D2DCryptoOps::DeriveNewKeyForPurpose(master_key, "client");
- std::unique_ptr<CryptoOps::SecretKey> server_key =
- D2DCryptoOps::DeriveNewKeyForPurpose(master_key, "server");
- if (!client_key || !server_key) {
- SetError("Failed to derive client or server key.");
- return nullptr;
- }
-
- handshake_state_ = InternalState::HANDSHAKE_ALREADY_USED;
-
- return std::unique_ptr<D2DConnectionContextV1>(new D2DConnectionContextV1(
- handshake_role_ == HandshakeRole::CLIENT ? *client_key : *server_key,
- handshake_role_ == HandshakeRole::CLIENT ? *server_key : *client_key,
- 0 /* initial encode sequence number */,
- 0 /* initial decode sequence number */));
-}
-
-UKey2Handshake::ParseResult UKey2Handshake::ParseClientInitUkey2Message(
- const string& handshake_message) {
- // Deserialize the protobuf.
- Ukey2Message message;
- if (!message.ParseFromString(handshake_message)) {
- return CreateFailedResultWithAlert(Ukey2Alert::BAD_MESSAGE,
- "Can't parse message 1.");
- }
-
- // Verify that message_type == CLIENT_INIT.
- if (!message.has_message_type() ||
- message.message_type() != Ukey2Message::CLIENT_INIT) {
- return CreateFailedResultWithAlert(
- Ukey2Alert::BAD_MESSAGE,
- "Expected, but did not find ClientInit message type.");
- }
-
- // Derserialize message_data as a ClientInit message.
- if (!message.has_message_data()) {
- return CreateFailedResultWithAlert(
- Ukey2Alert::BAD_MESSAGE_DATA,
- "Expected message data, but did not find it.");
- }
-
- Ukey2ClientInit client_init;
- if (!client_init.ParseFromString(message.message_data())) {
- return CreateFailedResultWithAlert(
- Ukey2Alert::BAD_MESSAGE_DATA,
- "Can't parse message data into ClientInit.");
- }
-
- // Check that version == VERSION.
- if (!client_init.has_version()) {
- return CreateFailedResultWithAlert(Ukey2Alert::BAD_VERSION,
- "ClientInit missing version.");
- }
- if (client_init.version() != kVersion) {
- return CreateFailedResultWithAlert(Ukey2Alert::BAD_VERSION,
- "ClientInit version mismatch.");
- }
-
- // Check that random is exactly kNonceLengthInBytes.
- if (!client_init.has_random()) {
- return CreateFailedResultWithAlert(Ukey2Alert::BAD_RANDOM,
- "ClientInit missing random.");
- }
- if (client_init.random().length() != kNonceLengthInBytes) {
- return CreateFailedResultWithAlert(
- Ukey2Alert::BAD_RANDOM, "ClientInit has incorrect nonce length.");
- }
-
- // Check to see if any of the handshake_cipher in handshake_cipher_commitment
- // are acceptable. Servers should select the first ahdnshake_cipher that it
- // finds acceptable to support clients signalling deprecated but supported
- // HandshakeCiphers. If no handshake_cipher is acceptable (or there are no
- // HandshakeCiphers in the message), the server sends a BAD_HANDSHAKE_CIPHER
- // alert message.
- if (client_init.cipher_commitments_size() == 0) {
- return CreateFailedResultWithAlert(
- Ukey2Alert::BAD_HANDSHAKE_CIPHER,
- "ClientInit is missing cipher commitments.");
- }
-
- for (const Ukey2ClientInit::CipherCommitment& commitment :
- client_init.cipher_commitments()) {
- if (!commitment.has_handshake_cipher() || !commitment.has_commitment() ||
- commitment.commitment().empty()) {
- return CreateFailedResultWithAlert(
- Ukey2Alert::BAD_HANDSHAKE_CIPHER,
- "ClientInit has improperly formatted cipher commitment.");
- }
-
- // TODO(aczeskis): for now we only support one cipher, eventually support
- // more.
- if (commitment.handshake_cipher() == static_cast<int>(handshake_cipher_)) {
- peer_commitment_ = commitment.commitment();
- }
- }
-
- if (peer_commitment_.empty()) {
- return CreateFailedResultWithAlert(Ukey2Alert::BAD_HANDSHAKE_CIPHER,
- "No acceptable commitments found");
- }
-
- // Checks that next_protocol contains a protocol that the server supports. We
- // currently only support one protocol.
- if (!client_init.has_next_protocol() ||
- client_init.next_protocol() != kNextProtocol) {
- return CreateFailedResultWithAlert(Ukey2Alert::BAD_NEXT_PROTOCOL,
- "Incorrect next protocol.");
- }
-
- // Store raw message for AUTH_STRING computation.
- wrapped_client_init_ = handshake_message;
- handshake_state_ = InternalState::SERVER_AFTER_CLIENT_INIT;
- return CreateSuccessResult();
-}
-
-UKey2Handshake::ParseResult UKey2Handshake::ParseServerInitUkey2Message(
- const string& handshake_message) {
- // Deserialize the protobuf.
- Ukey2Message message;
- if (!message.ParseFromString(handshake_message)) {
- return CreateFailedResultWithAlert(Ukey2Alert::BAD_MESSAGE,
- "Can't parse message 2.");
- }
-
- // Verify that message_type == SERVER_INIT.
- if (!message.has_message_type() ||
- message.message_type() != Ukey2Message::SERVER_INIT) {
- return CreateFailedResultWithAlert(
- Ukey2Alert::BAD_MESSAGE,
- "Expected, but did not find SERVER_INIT message type.");
- }
-
- // Derserialize message_data as a ServerInit message.
- if (!message.has_message_data()) {
- return CreateFailedResultWithAlert(
- Ukey2Alert::BAD_MESSAGE_DATA,
- "Expected message data, but did not find it.");
- }
-
- Ukey2ServerInit server_init;
- if (!server_init.ParseFromString(message.message_data())) {
- return CreateFailedResultWithAlert(
- Ukey2Alert::BAD_MESSAGE_DATA,
- "Can't parse message data into ServerInit.");
- }
-
- // Check that version == VERSION.
- if (!server_init.has_version()) {
- return CreateFailedResultWithAlert(Ukey2Alert::BAD_VERSION,
- "ServerInit missing version.");
- }
- if (server_init.version() != kVersion) {
- return CreateFailedResultWithAlert(Ukey2Alert::BAD_VERSION,
- "ServerInit version mismatch.");
- }
-
- // Check that random is exactly kNonceLengthInBytes.
- if (!server_init.has_random()) {
- return CreateFailedResultWithAlert(Ukey2Alert::BAD_RANDOM,
- "ServerInit missing random.");
- }
- if (server_init.random().length() != kNonceLengthInBytes) {
- return CreateFailedResultWithAlert(
- Ukey2Alert::BAD_RANDOM, "ServerInit has incorrect nonce length.");
- }
-
- // Check that the handshake_cipher matches a handshake cipher that was sent in
- // ClientInit::cipher_commitments().
- if (!server_init.has_handshake_cipher()) {
- return CreateFailedResultWithAlert(Ukey2Alert::BAD_HANDSHAKE_CIPHER,
- "No handshake cipher found.");
- }
-
- Ukey2HandshakeCipher cipher = server_init.handshake_cipher();
- HandshakeCipher server_cipher;
- switch (static_cast<HandshakeCipher>(cipher)) {
- case HandshakeCipher::P256_SHA512:
- server_cipher = static_cast<HandshakeCipher>(cipher);
- break;
- default:
- return CreateFailedResultWithAlert(Ukey2Alert::BAD_HANDSHAKE_CIPHER,
- "No acceptable handshake found.");
- }
-
- // Check that public_key parses into a correct public key structure.
- if (!server_init.has_public_key()) {
- return CreateFailedResultWithAlert(Ukey2Alert::BAD_PUBLIC_KEY,
- "No public key found in ServerInit.");
- }
-
- their_public_key_ = ParsePublicKey(server_init.public_key());
- if (!their_public_key_) {
- return CreateFailedResultWithAlert(Ukey2Alert::BAD_PUBLIC_KEY,
- "Failed to parse public key.");
- }
-
- // Store raw message for AUTH_STRING computation.
- wrapped_server_init_ = handshake_message;
- handshake_state_ = InternalState::CLIENT_AFTER_SERVER_INIT;
- return CreateSuccessResult();
-}
-
-UKey2Handshake::ParseResult UKey2Handshake::ParseClientFinishUkey2Message(
- const string& handshake_message) {
- // Deserialize the protobuf.
- Ukey2Message message;
- if (!message.ParseFromString(handshake_message)) {
- return CreateFailedResultWithoutAlert("Can't parse message 3.");
- }
-
- // Verify that message_type == CLIENT_FINISH.
- if (!message.has_message_type() ||
- message.message_type() != Ukey2Message::CLIENT_FINISH) {
- return CreateFailedResultWithoutAlert(
- "Expected, but did not find CLIENT_FINISH message type.");
- }
-
- // Verify that the hash of the CLientFinished message matches the expected
- // commitment from ClientInit.
- if (!VerifyCommitment(handshake_message)) {
- return CreateFailedResultWithoutAlert(last_error_);
- }
-
- // Deserialize message_data as a ClientFinished message.
- if (!message.has_message_data()) {
- return CreateFailedResultWithoutAlert(
- "Expected message data, but didn't find it.");
- }
-
- Ukey2ClientFinished client_finished;
- if (!client_finished.ParseFromString(message.message_data())) {
- return CreateFailedResultWithoutAlert("Failed to parse ClientFinished.");
- }
-
- // Check that public_key parses into a correct public key structure.
- if (!client_finished.has_public_key()) {
- return CreateFailedResultWithoutAlert(
- "No public key found in ClientFinished.");
- }
-
- their_public_key_ = ParsePublicKey(client_finished.public_key());
- if (!their_public_key_) {
- return CreateFailedResultWithoutAlert("Failed to parse public key.");
- }
-
- handshake_state_ = InternalState::HANDSHAKE_VERIFICATION_NEEDED;
- return CreateSuccessResult();
-}
-
-UKey2Handshake::ParseResult UKey2Handshake::CreateFailedResultWithAlert(
- Ukey2Alert::AlertType alert_type, const string& error_message) {
- if (!Ukey2Alert_AlertType_IsValid(alert_type)) {
- std::ostringstream stream;
- stream << "Unknown alert type: " << static_cast<int>(alert_type);
- SetError(stream.str());
- return {false, nullptr};
- }
-
- Ukey2Alert alert;
- alert.set_type(alert_type);
- if (!error_message.empty()) {
- alert.set_error_message(error_message);
- }
-
- std::unique_ptr<string> alert_message =
- MakeUkey2Message(Ukey2Message::ALERT, alert.SerializeAsString());
-
- SetError(error_message);
- ParseResult result{false, std::move(alert_message)};
- return result;
-}
-
-UKey2Handshake::ParseResult
-UKey2Handshake::CreateFailedResultWithoutAlert(const string& error_message) {
- SetError(error_message);
- return {false, nullptr};
-}
-
-UKey2Handshake::ParseResult UKey2Handshake::CreateSuccessResult() {
- return {true, nullptr};
-}
-
-bool UKey2Handshake::VerifyCommitment(const string& handshake_message) {
- std::unique_ptr<ByteBuffer> actual_client_finish_hash;
- switch (handshake_cipher_) {
- case HandshakeCipher::P256_SHA512:
- actual_client_finish_hash =
- CryptoOps::Sha512(ByteBuffer(handshake_message));
- break;
- default:
- // Unreachable.
- return false;
- }
-
- if (!actual_client_finish_hash) {
- SetError("Failed to hash ClientFinish message.");
- return false;
- }
-
- // Note: Equals() is a time constant comparison operation.
- if (!actual_client_finish_hash->Equals(peer_commitment_)) {
- SetError("Failed to verify commitment.");
- return false;
- }
-
- return true;
-}
-
-std::unique_ptr<Ukey2ClientInit::CipherCommitment>
-UKey2Handshake::GenerateP256Sha512Commitment() {
- // Generate the corresponding ClientFinished message if it's not done yet.
- if (raw_message3_map_.count(HandshakeCipher::P256_SHA512) == 0) {
- if (!our_key_pair_ || !our_key_pair_->public_key) {
- SetError("Invalid public key.");
- return nullptr;
- }
-
- std::unique_ptr<GenericPublicKey> generic_public_key =
- PublicKeyProtoUtil::EncodePublicKey(*(our_key_pair_->public_key));
- if (!generic_public_key) {
- SetError("Failed to encode generic public key.");
- return nullptr;
- }
-
- Ukey2ClientFinished client_finished;
- client_finished.set_public_key(generic_public_key->SerializeAsString());
- std::unique_ptr<string> serialized_ukey2_message = MakeUkey2Message(
- Ukey2Message::CLIENT_FINISH, client_finished.SerializeAsString());
- if (!serialized_ukey2_message) {
- SetError("Failed to serialized Ukey2Message.");
- return nullptr;
- }
-
- raw_message3_map_[HandshakeCipher::P256_SHA512] = *serialized_ukey2_message;
- }
-
- // Create the SHA512 commitment from raw message 3.
- std::unique_ptr<ByteBuffer> commitment = CryptoOps::Sha512(
- ByteBuffer(raw_message3_map_[HandshakeCipher::P256_SHA512]));
- if (!commitment) {
- SetError("Failed to hash message for commitment.");
- return nullptr;
- }
-
- // Wrap the commitment in a proto.
- std::unique_ptr<Ukey2ClientInit::CipherCommitment>
- handshake_cipher_commitment(new Ukey2ClientInit::CipherCommitment());
- handshake_cipher_commitment->set_handshake_cipher(P256_SHA512);
- handshake_cipher_commitment->set_commitment(commitment->String());
-
- return handshake_cipher_commitment;
-}
-
-std::unique_ptr<string> UKey2Handshake::MakeClientInitUkey2Message() {
- std::unique_ptr<ByteBuffer> nonce =
- CryptoOps::SecureRandom(kNonceLengthInBytes);
- if (!nonce) {
- SetError("Failed to generate nonce.");
- return nullptr;
- }
-
- Ukey2ClientInit client_init;
- client_init.set_version(kVersion);
- client_init.set_random(nonce->String());
- client_init.set_next_protocol(kNextProtocol);
-
- // At the moment, we only support one cipher.
- std::unique_ptr<Ukey2ClientInit::CipherCommitment>
- handshake_cipher_commitment = GenerateP256Sha512Commitment();
- if (!handshake_cipher_commitment) {
- // |last_error_| already set.
- return nullptr;
- }
- *(client_init.add_cipher_commitments()) = *handshake_cipher_commitment;
-
- return MakeUkey2Message(Ukey2Message::CLIENT_INIT,
- client_init.SerializeAsString());
-}
-
-std::unique_ptr<string> UKey2Handshake::MakeServerInitUkey2Message() {
- std::unique_ptr<ByteBuffer> nonce =
- CryptoOps::SecureRandom(kNonceLengthInBytes);
- if (!nonce) {
- SetError("Failed to generate nonce.");
- return nullptr;
- }
-
- if (!our_key_pair_ || !our_key_pair_->public_key) {
- SetError("Invalid key pair.");
- return nullptr;
- }
-
- std::unique_ptr<GenericPublicKey> public_key =
- PublicKeyProtoUtil::EncodePublicKey(*(our_key_pair_->public_key));
- if (!public_key) {
- SetError("Failed to encode public key.");
- return nullptr;
- }
-
- Ukey2ServerInit server_init;
- server_init.set_version(kVersion);
- server_init.set_random(nonce->String());
- server_init.set_handshake_cipher(
- static_cast<Ukey2HandshakeCipher>(handshake_cipher_));
- server_init.set_public_key(public_key->SerializeAsString());
-
- return MakeUkey2Message(Ukey2Message::SERVER_INIT,
- server_init.SerializeAsString());
-}
-
-// Generates the serialized representation of a Ukey2Message based on the
-// provided |type| and |data|. On error, returns nullptr and writes error
-// message to |out_error|.
-std::unique_ptr<string> UKey2Handshake::MakeUkey2Message(
- Ukey2Message::Type type, const string& data) {
- Ukey2Message message;
- if (!Ukey2Message::Type_IsValid(type)) {
- std::ostringstream stream;
- stream << "Invalid message type: " << type;
- SetError(stream.str());
- return nullptr;
- }
- message.set_message_type(type);
-
- // Only ALERT messages can have a blank data field.
- if (type != Ukey2Message::ALERT) {
- if (data.length() == 0) {
- SetError("Cannot send empty message data for non-alert messages");
- return nullptr;
- }
- }
- message.set_message_data(data);
-
- std::unique_ptr<string> serialized(new string());
- message.SerializeToString(serialized.get());
- return serialized;
-}
-
-void UKey2Handshake::SetError(const string& error_message) {
- handshake_state_ = InternalState::HANDSHAKE_ERROR;
- last_error_ = error_message;
-}
-
-} // namespace securegcm
diff --git a/src/main/cpp/src/securegcm/ukey2_shell.cc b/src/main/cpp/src/securegcm/ukey2_shell.cc
deleted file mode 100644
index 99a35a8..0000000
--- a/src/main/cpp/src/securegcm/ukey2_shell.cc
+++ /dev/null
@@ -1,297 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// The ukey2_shell binary is a command-line based wrapper, exercising the
-// UKey2Handshake class. Its main use is to be run in a Java test, testing the
-// compatibility of the Java and C++ implementations.
-//
-// This program can be run in two modes, initiator or responder (default is
-// initiator):
-// ukey2_shell --mode=initiator --verification_string_length=32
-// ukey2_shell --mode=responder --verification_string_length=32
-//
-// In initiator mode, the program performs the initiator handshake, and in
-// responder mode, it performs the responder handshake.
-//
-// After the handshake is done, the program establishes a secure connection and
-// enters a loop in which it processes the following commands:
-// * encrypt <payload>: encrypts the payload and prints it.
-// * decrypt <message>: decrypts the message and prints the payload.
-// * session_unique: prints the session unique value.
-//
-// IO is performed on stdin and stdout. To provide frame control, all frames
-// will have the following simple format:
-// [ length | bytes ]
-// where |length| is a 4 byte big-endian encoded unsigned integer.
-#include <cassert>
-#include <cstdio>
-#include <iostream>
-#include <memory>
-
-#include "securegcm/ukey2_handshake.h"
-#include "absl/container/fixed_array.h"
-#include "absl/flags/flag.h"
-#include "absl/flags/parse.h"
-
-#define LOG(ERROR) std::cerr
-#define CHECK_EQ(a, b) do { if ((a) != (b)) abort(); } while(0)
-
-ABSL_FLAG(
- int, verification_string_length, 32,
- "The length in bytes of the verification string. Must be a value between 1"
- "and 32.");
-ABSL_FLAG(string, mode, "initiator",
- "The mode to run as: one of [initiator, responder]");
-
-namespace securegcm {
-
-namespace {
-
-// Writes |message| to stdout in the frame format.
-void WriteFrame(const string& message) {
- // Write length of |message| in little-endian.
- const uint32_t length = message.length();
- fputc((length >> (3 * 8)) & 0xFF, stdout);
- fputc((length >> (2 * 8)) & 0xFF, stdout);
- fputc((length >> (1 * 8)) & 0xFF, stdout);
- fputc((length >> (0 * 8)) & 0xFF, stdout);
-
- // Write message to stdout.
- CHECK_EQ(message.length(),
- fwrite(message.c_str(), 1, message.length(), stdout));
- CHECK_EQ(0, fflush(stdout));
-}
-
-// Returns a message read from stdin after parsing it from the frame format.
-string ReadFrame() {
- // Read length of the frame from the stream.
- uint8_t length_data[sizeof(uint32_t)];
- CHECK_EQ(sizeof(uint32_t), fread(&length_data, 1, sizeof(uint32_t), stdin));
-
- uint32_t length = 0;
- length |= static_cast<uint32_t>(length_data[0]) << (3 * 8);
- length |= static_cast<uint32_t>(length_data[1]) << (2 * 8);
- length |= static_cast<uint32_t>(length_data[2]) << (1 * 8);
- length |= static_cast<uint32_t>(length_data[3]) << (0 * 8);
-
- // Read |length| bytes from the stream.
- absl::FixedArray<char> buffer(length);
- CHECK_EQ(length, fread(buffer.data(), 1, length, stdin));
-
- return string(buffer.data(), length);
-}
-
-} // namespace
-
-// Handles the runtime of the program in initiator or responder mode.
-class UKey2Shell {
- public:
- explicit UKey2Shell(int verification_string_length);
- ~UKey2Shell();
-
- // Runs the shell, performing the initiator handshake for authentication.
- bool RunAsInitiator();
-
- // Runs the shell, performing the responder handshake for authentication.
- bool RunAsResponder();
-
- private:
- // Writes the next handshake message obtained from |ukey2_handshake_| to
- // stdout.
- // If an error occurs, |tag| is logged.
- bool WriteNextHandshakeMessage(const string& tag);
-
- // Reads the next handshake message from stdin and parses it using
- // |ukey2_handshake_|.
- // If an error occurs, |tag| is logged.
- bool ReadNextHandshakeMessage(const string& tag);
-
- // Writes the verification string to stdout and waits for a confirmation from
- // stdin.
- bool ConfirmVerificationString();
-
- // After authentication is completed, this function runs the loop handing the
- // secure connection.
- bool RunSecureConnectionLoop();
-
- std::unique_ptr<UKey2Handshake> ukey2_handshake_;
- const int verification_string_length_;
-};
-
-UKey2Shell::UKey2Shell(int verification_string_length)
- : verification_string_length_(verification_string_length) {}
-
-UKey2Shell::~UKey2Shell() {}
-
-bool UKey2Shell::WriteNextHandshakeMessage(const string& tag) {
- const std::unique_ptr<string> message =
- ukey2_handshake_->GetNextHandshakeMessage();
- if (!message) {
- LOG(ERROR) << "Failed to create [" << tag
- << "] message: " << ukey2_handshake_->GetLastError();
- return false;
- }
- WriteFrame(*message);
- return true;
-}
-
-bool UKey2Shell::ReadNextHandshakeMessage(const string& tag) {
- const string message = ReadFrame();
- const UKey2Handshake::ParseResult result =
- ukey2_handshake_->ParseHandshakeMessage(message);
- if (!result.success) {
- LOG(ERROR) << "Failed to parse [" << tag
- << "] message: " << ukey2_handshake_->GetLastError();
- if (result.alert_to_send) {
- WriteFrame(*result.alert_to_send);
- }
- return false;
- }
- return true;
-}
-
-bool UKey2Shell::ConfirmVerificationString() {
- const std::unique_ptr<string> auth_string =
- ukey2_handshake_->GetVerificationString(verification_string_length_);
- if (!auth_string) {
- LOG(ERROR) << "Failed to get verification string: "
- << ukey2_handshake_->GetLastError();
- return false;
- }
- WriteFrame(*auth_string);
-
- // Wait for ack message.
- const string message = ReadFrame();
- if (message != "ok") {
- LOG(ERROR) << "Expected string 'ok'";
- return false;
- }
- ukey2_handshake_->VerifyHandshake();
- return true;
-}
-
-bool UKey2Shell::RunSecureConnectionLoop() {
- const std::unique_ptr<D2DConnectionContextV1> connection_context =
- ukey2_handshake_->ToConnectionContext();
- if (!connection_context) {
- LOG(ERROR) << "Failed to create connection context: "
- << ukey2_handshake_->GetLastError();
- return false;
- }
-
- for (;;) {
- // Parse the next expression.
- const string expression = ReadFrame();
- const size_t pos = expression.find(" ");
- if (pos == std::string::npos) {
- LOG(ERROR) << "Invalid command in connection loop.";
- return false;
- }
- const string command = expression.substr(0, pos);
-
- if (command == "encrypt") {
- const string payload = expression.substr(pos + 1, expression.length());
- std::unique_ptr<string> encoded_message =
- connection_context->EncodeMessageToPeer(payload);
- if (!encoded_message) {
- LOG(ERROR) << "Failed to encode payload of size " << payload.length();
- return false;
- }
- WriteFrame(*encoded_message);
- } else if (command == "decrypt") {
- const string message = expression.substr(pos + 1, expression.length());
- std::unique_ptr<string> decoded_payload =
- connection_context->DecodeMessageFromPeer(message);
- if (!decoded_payload) {
- LOG(ERROR) << "Failed to decode message of size " << message.length();
- return false;
- }
- WriteFrame(*decoded_payload);
- } else if (command == "session_unique") {
- std::unique_ptr<string> session_unique =
- connection_context->GetSessionUnique();
- if (!session_unique) {
- LOG(ERROR) << "Failed to get session unique.";
- return false;
- }
- WriteFrame(*session_unique);
- } else {
- LOG(ERROR) << "Unrecognized command: " << command;
- return false;
- }
- }
-}
-
-bool UKey2Shell::RunAsInitiator() {
- ukey2_handshake_ = UKey2Handshake::ForInitiator(
- UKey2Handshake::HandshakeCipher::P256_SHA512);
- if (!ukey2_handshake_) {
- LOG(ERROR) << "Unable to create UKey2Handshake";
- return false;
- }
-
- // Perform handshake.
- if (!WriteNextHandshakeMessage("Initiator Init")) return false;
- if (!ReadNextHandshakeMessage("Responder Init")) return false;
- if (!WriteNextHandshakeMessage("Initiator Finish")) return false;
- if (!ConfirmVerificationString()) return false;
-
- // Create a connection context.
- return RunSecureConnectionLoop();
-}
-
-bool UKey2Shell::RunAsResponder() {
- ukey2_handshake_ = UKey2Handshake::ForResponder(
- UKey2Handshake::HandshakeCipher::P256_SHA512);
- if (!ukey2_handshake_) {
- LOG(ERROR) << "Unable to create UKey2Handshake";
- return false;
- }
-
- // Perform handshake.
- if (!ReadNextHandshakeMessage("Initiator Init")) return false;
- if (!WriteNextHandshakeMessage("Responder Init")) return false;
- if (!ReadNextHandshakeMessage("Initiator Finish")) return false;
- if (!ConfirmVerificationString()) return false;
-
- // Create a connection context.
- return RunSecureConnectionLoop();
-}
-
-} // namespace securegcm
-
-int main(int argc, char** argv) {
- absl::ParseCommandLine(argc, argv);
-
- const int verification_string_length =
- absl::GetFlag(FLAGS_verification_string_length);
- if (verification_string_length < 1 || verification_string_length > 32) {
- LOG(ERROR) << "Invalid flag value, verification_string_length: "
- << verification_string_length;
- return 1;
- }
-
- securegcm::UKey2Shell shell(verification_string_length);
- int exit_code = 0;
- const string mode = absl::GetFlag(FLAGS_mode);
- if (mode == "initiator") {
- exit_code = !shell.RunAsInitiator();
- } else if (mode == "responder") {
- exit_code = !shell.RunAsResponder();
- } else {
- LOG(ERROR) << "Invalid flag value, mode: " << mode;
- exit_code = 1;
- }
- return exit_code;
-}
diff --git a/src/main/cpp/test/securegcm/CMakeLists.txt b/src/main/cpp/test/securegcm/CMakeLists.txt
deleted file mode 100644
index 272f919..0000000
--- a/src/main/cpp/test/securegcm/CMakeLists.txt
+++ /dev/null
@@ -1,31 +0,0 @@
-# Copyright 2020 Google LLC
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# https://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-add_executable(ukey2_test
- d2d_connection_context_v1_test.cc
- d2d_crypto_ops_test.cc
- java_util_test.cc
-)
-
-target_link_libraries(ukey2_test
- PUBLIC
- ukey2
- gtest
- gtest_main
-)
-
-add_test(
- NAME ukey2_test
- COMMAND ukey2_test
-)
diff --git a/src/main/cpp/test/securegcm/d2d_connection_context_v1_test.cc b/src/main/cpp/test/securegcm/d2d_connection_context_v1_test.cc
deleted file mode 100644
index daf69d1..0000000
--- a/src/main/cpp/test/securegcm/d2d_connection_context_v1_test.cc
+++ /dev/null
@@ -1,124 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "securegcm/d2d_connection_context_v1.h"
-
-#include "securegcm/d2d_crypto_ops.h"
-#include "gtest/gtest.h"
-
-namespace securegcm {
-
-using securemessage::CryptoOps;
-
-namespace {
-
-// The encode and decode keys should be 32 bytes.
-const char kEncodeKeyData[] = "initiator_encode_key_for_aes_256";
-const char kDecodeKeyData[] = "initiator_decode_key_for_aes_256";
-
-} // namespace
-
-// A friend to access the private variables of D2DConnectionContextV1.
-class D2DConnectionContextV1Peer {
- public:
- explicit D2DConnectionContextV1Peer(const std::string& savedSessionInfo) {
- context_ = D2DConnectionContextV1::FromSavedSession(savedSessionInfo);
- }
-
- D2DConnectionContextV1* GetContext() { return context_.get(); }
-
- uint32_t GetEncodeSequenceNumber() {
- return context_->encode_sequence_number_;
- }
-
- uint32_t GetDecodeSequenceNumber() {
- return context_->decode_sequence_number_;
- }
-
- private:
- std::unique_ptr<D2DConnectionContextV1> context_;
-};
-
-TEST(D2DConnectionContextionV1Test, SaveSession) {
- CryptoOps::SecretKey encodeKey = CryptoOps::SecretKey(
- kEncodeKeyData, CryptoOps::KeyAlgorithm::AES_256_KEY);
- CryptoOps::SecretKey decodeKey = CryptoOps::SecretKey(
- kDecodeKeyData, CryptoOps::KeyAlgorithm::AES_256_KEY);
-
- D2DConnectionContextV1 initiator =
- D2DConnectionContextV1(encodeKey, decodeKey, 0, 1);
- D2DConnectionContextV1 responder =
- D2DConnectionContextV1(decodeKey, encodeKey, 1, 0);
-
- std::unique_ptr<std::string> initiatorSavedSessionState =
- initiator.SaveSession();
- std::unique_ptr<std::string> responderSavedSessionState =
- responder.SaveSession();
-
- D2DConnectionContextV1Peer restoredInitiator =
- D2DConnectionContextV1Peer(*initiatorSavedSessionState);
- D2DConnectionContextV1Peer restoredResponder =
- D2DConnectionContextV1Peer(*responderSavedSessionState);
-
- // Verify internal state matches initialization.
- EXPECT_EQ(0, restoredInitiator.GetEncodeSequenceNumber());
- EXPECT_EQ(1, restoredInitiator.GetDecodeSequenceNumber());
- EXPECT_EQ(1, restoredResponder.GetEncodeSequenceNumber());
- EXPECT_EQ(0, restoredResponder.GetDecodeSequenceNumber());
-
- EXPECT_EQ(*restoredInitiator.GetContext()->GetSessionUnique(),
- *restoredResponder.GetContext()->GetSessionUnique());
-
- const std::string message = "ping";
-
- // Ensure that they can still talk to one another.
- std::string encodedMessage =
- *restoredInitiator.GetContext()->EncodeMessageToPeer(message);
- std::string decodedMessage =
- *restoredResponder.GetContext()->DecodeMessageFromPeer(encodedMessage);
-
- EXPECT_EQ(message, decodedMessage);
-
- encodedMessage =
- *restoredResponder.GetContext()->EncodeMessageToPeer(message);
- decodedMessage =
- *restoredInitiator.GetContext()->DecodeMessageFromPeer(encodedMessage);
-
- EXPECT_EQ(message, decodedMessage);
-}
-
-TEST(D2DConnectionContextionV1Test, SaveSession_TooShort) {
- CryptoOps::SecretKey encodeKey = CryptoOps::SecretKey(
- kEncodeKeyData, CryptoOps::KeyAlgorithm::AES_256_KEY);
- CryptoOps::SecretKey decodeKey = CryptoOps::SecretKey(
- kDecodeKeyData, CryptoOps::KeyAlgorithm::AES_256_KEY);
-
- D2DConnectionContextV1 initiator =
- D2DConnectionContextV1(encodeKey, decodeKey, 0, 1);
-
- std::unique_ptr<std::string> initiatorSavedSessionState =
- initiator.SaveSession();
-
- // Try to rebuild the context with a shorter session state.
- std::string shortSessionState = initiatorSavedSessionState->substr(
- 0, initiatorSavedSessionState->size() - 1);
-
- D2DConnectionContextV1Peer restoredInitiator =
- D2DConnectionContextV1Peer(shortSessionState);
-
- // nullptr is returned on error. It should not crash.
- EXPECT_EQ(restoredInitiator.GetContext(), nullptr);
-}
-
-} // namespace securegcm
diff --git a/src/main/cpp/test/securegcm/d2d_crypto_ops_test.cc b/src/main/cpp/test/securegcm/d2d_crypto_ops_test.cc
deleted file mode 100644
index 5acbb89..0000000
--- a/src/main/cpp/test/securegcm/d2d_crypto_ops_test.cc
+++ /dev/null
@@ -1,158 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "securegcm/d2d_crypto_ops.h"
-
-#include "gtest/gtest.h"
-#include "securemessage/crypto_ops.h"
-#include "securemessage/secure_message_builder.h"
-
-namespace securegcm {
-
-using securemessage::CryptoOps;
-using securemessage::HeaderAndBody;
-using securemessage::SecureMessage;
-
-namespace {
-
-const char kPayloadData[] = "Test payload";
-const char kSecretKeyData[] = "secret key must be 32 bytes long";
-const char kOtherSecretKeyData[] = "other secret key****************";
-const char kInvalidSigncryptedMessage[] = "Not a protobuf";
-const int kSupportedProtocolVersion = 1;
-const int kUnsupportedProtocolVersion = 2;
-
-} // namespace
-
-class D2DCryptoOpsTest : public testing::Test {
- public:
- D2DCryptoOpsTest()
- : payload_(DEVICE_TO_DEVICE_MESSAGE, kPayloadData),
- secret_key_(kSecretKeyData, CryptoOps::AES_256_KEY) {}
-
- protected:
- const D2DCryptoOps::Payload payload_;
- const CryptoOps::SecretKey secret_key_;
-};
-
-TEST_F(D2DCryptoOpsTest, Signcrypt_EmptyPayload) {
- // Signcrypting an empty payload should fail.
- D2DCryptoOps::Payload empty_payload(DEVICE_TO_DEVICE_MESSAGE, string());
- EXPECT_FALSE(D2DCryptoOps::SigncryptPayload(empty_payload, secret_key_));
-}
-
-TEST_F(D2DCryptoOpsTest, VerifyDecrypt_InvalidMessage) {
- // VerifyDecrypting an invalid payload should fail.
- EXPECT_FALSE(D2DCryptoOps::VerifyDecryptPayload(kInvalidSigncryptedMessage,
- secret_key_));
-}
-
-TEST_F(D2DCryptoOpsTest, VerifyDecrypt_NoPublicMetadata) {
- std::unique_ptr<string> signcrypted_message =
- D2DCryptoOps::SigncryptPayload(payload_, secret_key_);
-
- // Clear metadata field in header.
- SecureMessage secure_message;
- ASSERT_TRUE(secure_message.ParseFromString(*signcrypted_message));
- HeaderAndBody header_and_body;
- ASSERT_TRUE(
- header_and_body.ParseFromString(secure_message.header_and_body()));
- header_and_body.mutable_header()->clear_public_metadata();
- secure_message.set_header_and_body(header_and_body.SerializeAsString());
-
- // Decrypting the message should now fail.
- EXPECT_FALSE(D2DCryptoOps::VerifyDecryptPayload(
- secure_message.SerializeAsString(), secret_key_));
-}
-
-TEST_F(D2DCryptoOpsTest, VerifyDecrypt_ProtocolVersionNotSupported) {
- std::unique_ptr<string> signcrypted_message =
- D2DCryptoOps::SigncryptPayload(payload_, secret_key_);
-
- // Change version in metadata field in header to an unsupported version.
- SecureMessage secure_message;
- ASSERT_TRUE(secure_message.ParseFromString(*signcrypted_message));
- HeaderAndBody header_and_body;
- ASSERT_TRUE(
- header_and_body.ParseFromString(secure_message.header_and_body()));
- GcmMetadata metadata;
- ASSERT_TRUE(
- metadata.ParseFromString(header_and_body.header().public_metadata()));
- EXPECT_EQ(kSupportedProtocolVersion, metadata.version());
- metadata.set_version(kUnsupportedProtocolVersion);
- header_and_body.mutable_header()->set_public_metadata(
- metadata.SerializeAsString());
- secure_message.set_header_and_body(header_and_body.SerializeAsString());
-
- // Decrypting the message should now fail.
- EXPECT_FALSE(D2DCryptoOps::VerifyDecryptPayload(
- secure_message.SerializeAsString(), secret_key_));
-}
-
-TEST_F(D2DCryptoOpsTest, SigncryptThenVerifyDecrypt_SuccessWithSameKey) {
- // Signcrypt the payload.
- std::unique_ptr<string> signcrypted_message =
- D2DCryptoOps::SigncryptPayload(payload_, secret_key_);
- ASSERT_TRUE(signcrypted_message);
-
- // Decrypt the signcrypted message.
- std::unique_ptr<D2DCryptoOps::Payload> decrypted_payload =
- D2DCryptoOps::VerifyDecryptPayload(*signcrypted_message, secret_key_);
- ASSERT_TRUE(decrypted_payload);
-
- // Check that decrypted payload is the same.
- EXPECT_EQ(payload_.type(), decrypted_payload->type());
- EXPECT_EQ(payload_.message(), decrypted_payload->message());
-}
-
-TEST_F(D2DCryptoOpsTest, SigncryptThenVerifyDecrypt_FailsWithDifferentKey) {
- CryptoOps::SecretKey other_secret_key(kOtherSecretKeyData,
- CryptoOps::AES_256_KEY);
-
- // Signcrypt the payload with first secret key.
- std::unique_ptr<string> signcrypted_message =
- D2DCryptoOps::SigncryptPayload(payload_, secret_key_);
- ASSERT_TRUE(signcrypted_message);
-
- // Decrypting the signcrypted message with the other secret key should fail.
- EXPECT_FALSE(D2DCryptoOps::VerifyDecryptPayload(*signcrypted_message,
- other_secret_key));
-}
-
-TEST_F(D2DCryptoOpsTest, DeriveNewKeyForPurpose_ClientServer) {
- CryptoOps::SecretKey master_key(kSecretKeyData, CryptoOps::AES_256_KEY);
-
- std::unique_ptr<CryptoOps::SecretKey> derived_key1 =
- D2DCryptoOps::DeriveNewKeyForPurpose(master_key, "client");
- std::unique_ptr<CryptoOps::SecretKey> derived_key2 =
- D2DCryptoOps::DeriveNewKeyForPurpose(master_key, "server");
-
- ASSERT_TRUE(derived_key1);
- ASSERT_TRUE(derived_key2);
- EXPECT_EQ(CryptoOps::AES_256_KEY, derived_key1->algorithm());
- EXPECT_EQ(CryptoOps::AES_256_KEY, derived_key2->algorithm());
- EXPECT_NE(derived_key1->data().String(), derived_key2->data().String());
-}
-
-TEST_F(D2DCryptoOpsTest, DeriveNewKeyForPurpose_InvalidMasterKeySize) {
- CryptoOps::SecretKey master_key("Invalid Size", CryptoOps::AES_256_KEY);
- EXPECT_FALSE(D2DCryptoOps::DeriveNewKeyForPurpose(master_key, "purpose"));
-}
-
-TEST_F(D2DCryptoOpsTest, DeriveNewKeyForPurpose_PurposeEmpty) {
- CryptoOps::SecretKey master_key(kSecretKeyData, CryptoOps::AES_256_KEY);
- EXPECT_FALSE(D2DCryptoOps::DeriveNewKeyForPurpose(master_key, string()));
-}
-
-} // namespace securegcm
diff --git a/src/main/cpp/test/securegcm/java_util_test.cc b/src/main/cpp/test/securegcm/java_util_test.cc
deleted file mode 100644
index 20928fa..0000000
--- a/src/main/cpp/test/securegcm/java_util_test.cc
+++ /dev/null
@@ -1,84 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "securegcm/java_util.h"
-
-#include <limits>
-
-#include "gtest/gtest.h"
-
-namespace securegcm {
-
-using securemessage::ByteBuffer;
-
-namespace {
-
-int32_t kMinInt32 = std::numeric_limits<int32_t>::min();
-int32_t kMaxInt32 = std::numeric_limits<int32_t>::max();
-
-} // namespace
-
-
-TEST(JavaUtilTest, TestJavaAdd_InRange) {
- EXPECT_EQ(2, java_util::JavaAdd(1, 1));
-}
-
-TEST(JavaUtilTest, TestJavaAdd_Underflow) {
- EXPECT_EQ(kMaxInt32, java_util::JavaAdd(kMinInt32, -1));
- EXPECT_EQ(kMaxInt32 - 1, java_util::JavaAdd(kMinInt32, -2));
- EXPECT_EQ(1, java_util::JavaAdd(kMinInt32, -kMaxInt32));
-}
-
-TEST(JavaUtilTest, TestJavaAdd_Overflow) {
- EXPECT_EQ(kMinInt32, java_util::JavaAdd(kMaxInt32, 1));
- EXPECT_EQ(kMinInt32 + 1, java_util::JavaAdd(kMaxInt32, 2));
- EXPECT_EQ(-2, java_util::JavaAdd(kMaxInt32, kMaxInt32));
-}
-
-TEST(JavaUtilTest, TestJavaMultiply_InRange) {
- EXPECT_EQ(4, java_util::JavaAdd(2, 2));
-}
-
-TEST(JavaUtilTest, TestJavaMultiply_Underflow) {
- EXPECT_EQ(0, java_util::JavaMultiply(kMinInt32, 2));
- EXPECT_EQ(-(kMinInt32 / 2), java_util::JavaMultiply(kMinInt32 / 2, 3));
- EXPECT_EQ(kMinInt32, java_util::JavaMultiply(kMinInt32, kMaxInt32));
-}
-
-TEST(JavaUtilTest, TestJavaMultiply_Overflow) {
- EXPECT_EQ(-2, java_util::JavaMultiply(kMaxInt32, 2));
- EXPECT_EQ(kMaxInt32 - 2, java_util::JavaMultiply(kMaxInt32, 3));
- EXPECT_EQ(1, java_util::JavaMultiply(kMaxInt32, kMaxInt32));
-}
-
-TEST(JavaUtilTest, TestJavaHashCode_EmptyBytes) {
- EXPECT_EQ(1, java_util::JavaHashCode(ByteBuffer()));
-}
-
-TEST(JavaUtilTest, TestJavaHashCode_LongByteArray) {
- const uint8_t kBytes[] = {
- 0x93, 0x75, 0xE1, 0x2E, 0x26, 0x28, 0x54, 0x8C, 0xD9, 0x5C, 0x48, 0x7A,
- 0x07, 0x53, 0x4E, 0xED, 0x28, 0x52, 0x5D, 0x41, 0xE3, 0x18, 0x84, 0x84,
- 0x5F, 0xF6, 0x89, 0x98, 0x25, 0x1E, 0xD9, 0x6C, 0x85, 0xF3, 0x5A, 0x83,
- 0x39, 0x37, 0x4E, 0x77, 0x95, 0xB5, 0x58, 0x7C, 0xD2, 0x55, 0xA0, 0x86,
- 0x13, 0x3F, 0xBF, 0x85, 0xD3, 0xE0, 0x28, 0x90, 0x17, 0x3D, 0x2E, 0xD4,
- 0x4D, 0x95, 0x9C, 0xAE, 0xAD, 0x8A, 0x05, 0x91, 0x5D, 0xC6, 0x4B, 0x09,
- 0xB2, 0xD9, 0x34, 0x64, 0x07, 0x7B, 0x07, 0x8C, 0xA6, 0xC7, 0x1C, 0x10,
- 0x34, 0xD4, 0x30, 0x80, 0x03, 0x4F, 0x2C, 0x70};
- const int32_t kExpectedHashCode = 1983685004;
- EXPECT_EQ(kExpectedHashCode,
- java_util::JavaHashCode(ByteBuffer(kBytes, sizeof(kBytes))));
-}
-
-} // namespace securegcm
diff --git a/src/main/java/com/google/security/annotations/CryptoAnnotation.java b/src/main/java/com/google/security/annotations/CryptoAnnotation.java
deleted file mode 100644
index 7f4230d..0000000
--- a/src/main/java/com/google/security/annotations/CryptoAnnotation.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/* Copyright 2018 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-// Copyright 2007 Google Inc. All Rights Reserved
-
-package com.google.security.annotations;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Crypto Key Annotation: Label any cryptographic keys in code with this
- * annotation. This will help identify cryptographic keys that are exposed in
- * source code. Keys in source code should be annotated with an owner, purpose,
- * removal priority, and leak severity.
- *
- * Example of usage:
- * @CryptoAnnotation(
- * purpose = CryptoAnnotation.Purpose.AUTHENTICATION,
- * owner = "sweis",
- * bugId = 7041243,
- * leakSeverity = CryptoAnnotation.LeakSeverity.S2,
- * removalPriority = CryptoAnnotation.RemovalPriority.P1,
- * description = "This key is used to sign blah blah blah."
- * removalDate = "9/2007
- * )
- * byte[] keyBytes = {0xDE, 0xAD, 0xBE, 0xEF};
- *
- * @author sweis@google.com (Steve Weis)
- */
-@Retention(RetentionPolicy.SOURCE)
-@Target({ElementType.FIELD, ElementType.LOCAL_VARIABLE})
-public @interface CryptoAnnotation {
- /*
- * Keys with "encryption" and "authentication" purposes should be removed
- * from source code.
- *
- * Keys with "obfuscation" and "integrity check" purposes do not necessarily
- * need to be cryptographically strong. They may or may not be removed from
- * code at the discretion of the code owner.
- */
- public enum Purpose {ENCRYPTION, AUTHENTICATION, OBFUSCATION,
- INTEGRITY_CHECK, PASSWORD, OTHER}
- public enum LeakSeverity {S0, S1, S2, S3, S4, NoRisk}
- public enum RemovalPriority {P0, P1, P2, P3, P4, WillNotFix}
-
- LeakSeverity leakSeverity();
- RemovalPriority removalPriority();
- int bugId() default 0;
- String owner(); // Will be contacted in the event a key is leaked
- Purpose purpose();
- String description() default "";
- String removalDate() default "";
-}
-
diff --git a/src/main/java/com/google/security/annotations/SuppressInsecureCipherModeCheckerNoReview.java b/src/main/java/com/google/security/annotations/SuppressInsecureCipherModeCheckerNoReview.java
deleted file mode 100644
index 93dc716..0000000
--- a/src/main/java/com/google/security/annotations/SuppressInsecureCipherModeCheckerNoReview.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/* Copyright 2018 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.security.annotations;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-import javax.crypto.Cipher;
-
-/**
- * This annotation is used to disable the InsecureCipherMode Error Prone checker for legacy code
- * that didn't undergo a security review by ISE.
- *
- * <p>A {@link Cipher} object is created using one of the overloads of the
- * {@link Cipher#getInstance()} method. This method takes a specification of the transformer either
- * as a triple "Algorithm/Mode/Padding" or just "Algorithm", using the provider's default settings.
- * The InsecureCipherMode checker implemented in Error Prone flags all call sites of
- * {@link Cipher#getInstance()}, where either the insecure ECB mode or the provider's default mode
- * is used. This method annotation is used to suppress the Error Prone checker for legacy code
- * without review by ISE. The annotation is BUILD-visibility restricted and every use must be vetted
- * by the ISE team.
- *
- * <p>Example of usage:
- * <pre>
- * {@code
- * @SuppressInsecureCipherModeCheckerNoReview
- * private String decrypt(String[] input) {
- * Cipher aesCipher = Cipher.getInstance("AES");
- * aesCipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(rawKeyMaterial, "AES"));
- * // ...
- * }
- * }
- * </pre>
- *
- * @author avenet@google.com (Arnaud J. Venet)
- *
- */
-@Documented
-@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.CONSTRUCTOR,
- ElementType.LOCAL_VARIABLE})
-@Retention(RetentionPolicy.SOURCE)
-public @interface SuppressInsecureCipherModeCheckerNoReview {}
diff --git a/src/main/java/com/google/security/annotations/SuppressInsecureCipherModeCheckerPendingReview.java b/src/main/java/com/google/security/annotations/SuppressInsecureCipherModeCheckerPendingReview.java
deleted file mode 100644
index a7957c1..0000000
--- a/src/main/java/com/google/security/annotations/SuppressInsecureCipherModeCheckerPendingReview.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/* Copyright 2018 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.security.annotations;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-import javax.crypto.Cipher;
-
-/**
- * This annotation is used to temporarily disable the InsecureCipherMode Error Prone checker while
- * the violation is being reviewed by ISE. A comment including a tracking bug for the ongoing
- * security review should accompany the annotation. If the specific use is deemed a valid exception
- * after review, the annotation should be changed to @SuppressInsecureCipherModeCheckerReviewed.
- *
- * <p>A {@link Cipher} object is created using one of the overloads of the
- * {@link Cipher#getInstance()} method. This method takes a specification of the transformer either
- * as a triple "Algorithm/Mode/Padding" or just "Algorithm", using the provider's default settings.
- * The InsecureCipherMode checker implemented in Error Prone flags all call sites of
- * {@link Cipher#getInstance()}, where either the insecure ECB mode or the provider's default mode
- * is used. This method annotation is used to suppress the Error Prone checker in use cases where an
- * exception has been granted by ISE after proper review. The annotation is BUILD-visibility
- * restricted and every use must be vetted by the ISE team.
- *
- * <p>Example of usage:
- * <pre>
- * {@code
- * @SuppressInsecureCipherModeCheckerPendingReview // Tracking bug for the review: b/...
- * private String decrypt(String[] input) {
- * Cipher aesCipher = Cipher.getInstance("AES");
- * aesCipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(rawKeyMaterial, "AES"));
- * // ...
- * }
- * }
- * </pre>
- *
- * @author avenet@google.com (Arnaud J. Venet)
- *
- */
-@Documented
-@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.CONSTRUCTOR,
- ElementType.LOCAL_VARIABLE})
-@Retention(RetentionPolicy.SOURCE)
-public @interface SuppressInsecureCipherModeCheckerPendingReview {}
diff --git a/src/main/java/com/google/security/annotations/SuppressInsecureCipherModeCheckerReviewed.java b/src/main/java/com/google/security/annotations/SuppressInsecureCipherModeCheckerReviewed.java
deleted file mode 100644
index d1780c5..0000000
--- a/src/main/java/com/google/security/annotations/SuppressInsecureCipherModeCheckerReviewed.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/* Copyright 2018 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.security.annotations;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-import javax.crypto.Cipher;
-
-/**
- * This annotation is used to disable the InsecureCipherMode Error Prone checker after a proper
- * review by ISE. A comment including a tracking bug for the security review should accompany the
- * annotation.
- *
- * <p>A {@link Cipher} object is created using one of the overloads of the
- * {@link Cipher#getInstance()} method. This method takes a specification of the transformer either
- * as a triple "Algorithm/Mode/Padding" or just "Algorithm", using the provider's default settings.
- * The InsecureCipherMode checker implemented in Error Prone flags all call sites of
- * {@link Cipher#getInstance()}, where either the insecure ECB mode or the provider's default mode
- * is used. This method annotation is used to suppress the Error Prone checker in use cases where an
- * exception has been granted by ISE after proper review. The annotation is BUILD-visibility
- * restricted and every use must be vetted by the ISE team.
- *
- * <p>Example of usage:
- * <pre>
- * {@code
- * @SuppressInsecureCipherModeCheckerReviewed // Tracking bug for the review: b/...
- * private String decrypt(String[] input) {
- * Cipher aesCipher = Cipher.getInstance("AES");
- * aesCipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(rawKeyMaterial, "AES"));
- * // ...
- * }
- * }
- * </pre>
- *
- * @author avenet@google.com (Arnaud J. Venet)
- *
- */
-@Documented
-@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.CONSTRUCTOR,
- ElementType.LOCAL_VARIABLE})
-@Retention(RetentionPolicy.SOURCE)
-public @interface SuppressInsecureCipherModeCheckerReviewed {}
diff --git a/src/main/java/com/google/security/cryptauth/lib/securegcm/D2DConnectionContext.java b/src/main/java/com/google/security/cryptauth/lib/securegcm/D2DConnectionContext.java
deleted file mode 100644
index fb4af63..0000000
--- a/src/main/java/com/google/security/cryptauth/lib/securegcm/D2DConnectionContext.java
+++ /dev/null
@@ -1,274 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.security.cryptauth.lib.securegcm;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.protobuf.ByteString;
-import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.security.cryptauth.lib.securegcm.DeviceToDeviceMessagesProto.DeviceToDeviceMessage;
-import com.google.security.cryptauth.lib.securegcm.TransportCryptoOps.Payload;
-import com.google.security.cryptauth.lib.securegcm.TransportCryptoOps.PayloadType;
-import java.io.UnsupportedEncodingException;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.security.SignatureException;
-import java.util.Arrays;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.SecretKeySpec;
-
-/**
- * The full context of a secure connection. This object has methods to encode and decode messages
- * that are to be sent to another device.
- *
- * Subclasses keep track of the keys shared with the other device, and of the sequence in which the
- * messages are expected.
- */
-public abstract class D2DConnectionContext {
- private static final String UTF8 = "UTF-8";
- private final int protocolVersion;
-
- protected D2DConnectionContext(int protocolVersion) {
- this.protocolVersion = protocolVersion;
- }
-
- /**
- * @return the version of the D2D protocol.
- */
- public int getProtocolVersion() {
- return protocolVersion;
- }
-
- /**
- * Once initiator and responder have exchanged public keys, use this method to encrypt and
- * sign a payload. Both initiator and responder devices can use this message.
- *
- * @param payload the payload that should be encrypted.
- */
- public byte[] encodeMessageToPeer(byte[] payload) {
- incrementSequenceNumberForEncoding();
- DeviceToDeviceMessage message = createDeviceToDeviceMessage(
- payload, getSequenceNumberForEncoding());
- try {
- return D2DCryptoOps.signcryptPayload(
- new Payload(PayloadType.DEVICE_TO_DEVICE_MESSAGE,
- message.toByteArray()),
- getEncodeKey());
- } catch (InvalidKeyException e) {
- // should never happen, since we agreed on the key earlier
- throw new RuntimeException(e);
- } catch (NoSuchAlgorithmException e) {
- // should never happen
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Encrypting/signing a string for transmission to another device.
- *
- * @see #encodeMessageToPeer(byte[])
- *
- * @param payload the payload that should be encrypted.
- */
- public byte[] encodeMessageToPeer(String payload) {
- try {
- return encodeMessageToPeer(payload.getBytes(UTF8));
- } catch (UnsupportedEncodingException e) {
- // Should never happen - we should always be able to UTF-8-encode a string
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Once InitiatorHello and ResponderHello(AndPayload) are exchanged, use this method
- * to decrypt and verify a message received from the other device. Both initiator and
- * responder device can use this message.
- *
- * @param message the message that should be encrypted.
- * @throws SignatureException if the message from the remote peer did not pass verification
- */
- public byte[] decodeMessageFromPeer(byte[] message) throws SignatureException {
- try {
- Payload payload = D2DCryptoOps.verifydecryptPayload(message, getDecodeKey());
- if (!PayloadType.DEVICE_TO_DEVICE_MESSAGE.equals(payload.getPayloadType())) {
- throw new SignatureException("wrong message type in device-to-device message");
- }
-
- DeviceToDeviceMessage messageProto = DeviceToDeviceMessage.parseFrom(payload.getMessage());
- incrementSequenceNumberForDecoding();
- if (messageProto.getSequenceNumber() != getSequenceNumberForDecoding()) {
- throw new SignatureException("Incorrect sequence number");
- }
-
- return messageProto.getMessage().toByteArray();
- } catch (InvalidKeyException e) {
- throw new SignatureException(e);
- } catch (NoSuchAlgorithmException e) {
- // this shouldn't happen - the algorithms are hard-coded.
- throw new RuntimeException(e);
- } catch (InvalidProtocolBufferException e) {
- throw new SignatureException(e);
- }
- }
-
- /**
- * Once InitiatorHello and ResponderHello(AndPayload) are exchanged, use this method
- * to decrypt and verify a message received from the other device. Both initiator and
- * responder device can use this message.
- *
- * @param message the message that should be encrypted.
- */
- public String decodeMessageFromPeerAsString(byte[] message) throws SignatureException {
- try {
- return new String(decodeMessageFromPeer(message), UTF8);
- } catch (UnsupportedEncodingException e) {
- // Should never happen - we should always be able to UTF-8-encode a string
- throw new RuntimeException(e);
- }
- }
-
- // package-private
- static DeviceToDeviceMessage createDeviceToDeviceMessage(byte[] message, int sequenceNumber) {
- DeviceToDeviceMessage.Builder deviceToDeviceMessage = DeviceToDeviceMessage.newBuilder();
- deviceToDeviceMessage.setSequenceNumber(sequenceNumber);
- deviceToDeviceMessage.setMessage(ByteString.copyFrom(message));
- return deviceToDeviceMessage.build();
- }
-
- /**
- * Returns a cryptographic digest (SHA256) of the session keys prepended by the SHA256 hash
- * of the ASCII string "D2D"
- * @throws NoSuchAlgorithmException if SHA 256 doesn't exist on this platform
- */
- public abstract byte[] getSessionUnique() throws NoSuchAlgorithmException;
-
- /**
- * Increments the sequence number used for encoding messages.
- */
- protected abstract void incrementSequenceNumberForEncoding();
-
- /**
- * Increments the sequence number used for decoding messages.
- */
- protected abstract void incrementSequenceNumberForDecoding();
-
- /**
- * @return the last sequence number used to encode a message.
- */
- @VisibleForTesting
- abstract int getSequenceNumberForEncoding();
-
- /**
- * @return the last sequence number used to decode a message.
- */
- @VisibleForTesting
- abstract int getSequenceNumberForDecoding();
-
- /**
- * @return the {@link SecretKey} used for encoding messages.
- */
- @VisibleForTesting
- abstract SecretKey getEncodeKey();
-
- /**
- * @return the {@link SecretKey} used for decoding messages.
- */
- @VisibleForTesting
- abstract SecretKey getDecodeKey();
-
- /**
- * Creates a saved session that can later be used for resumption. Note, this must be stored in a
- * secure location.
- *
- * @return the saved session, suitable for resumption.
- */
- public abstract byte[] saveSession();
-
- /**
- * Parse a saved session info and attempt to construct a resumed context.
- * The first byte in a saved session info must always be the protocol version.
- * Note that an {@link IllegalArgumentException} will be thrown if the savedSessionInfo is not
- * properly formatted.
- *
- * @return a resumed context from a saved session.
- */
- public static D2DConnectionContext fromSavedSession(byte[] savedSessionInfo) {
- if (savedSessionInfo == null || savedSessionInfo.length == 0) {
- throw new IllegalArgumentException("savedSessionInfo null or too short");
- }
-
- int protocolVersion = savedSessionInfo[0] & 0xff;
-
- switch (protocolVersion) {
- case 0:
- // Version 0 has a 1 byte protocol version, a 4 byte sequence number,
- // and 32 bytes of AES key (1 + 4 + 32 = 37)
- if (savedSessionInfo.length != 37) {
- throw new IllegalArgumentException("Incorrect data length (" + savedSessionInfo.length
- + ") for v0 protocol");
- }
- int sequenceNumber = bytesToSignedInt(Arrays.copyOfRange(savedSessionInfo, 1, 5));
- SecretKey sharedKey = new SecretKeySpec(Arrays.copyOfRange(savedSessionInfo, 5, 37), "AES");
- return new D2DConnectionContextV0(sharedKey, sequenceNumber);
-
- case 1:
- // Version 1 has a 1 byte protocol version, two 4 byte sequence numbers,
- // and two 32 byte AES keys (1 + 4 + 4 + 32 + 32 = 73)
- if (savedSessionInfo.length != 73) {
- throw new IllegalArgumentException("Incorrect data length for v1 protocol");
- }
- int encodeSequenceNumber = bytesToSignedInt(Arrays.copyOfRange(savedSessionInfo, 1, 5));
- int decodeSequenceNumber = bytesToSignedInt(Arrays.copyOfRange(savedSessionInfo, 5, 9));
- SecretKey encodeKey =
- new SecretKeySpec(Arrays.copyOfRange(savedSessionInfo, 9, 41), "AES");
- SecretKey decodeKey =
- new SecretKeySpec(Arrays.copyOfRange(savedSessionInfo, 41, 73), "AES");
- return new D2DConnectionContextV1(encodeKey, decodeKey, encodeSequenceNumber,
- decodeSequenceNumber);
-
- default:
- throw new IllegalArgumentException("Cannot rebuild context, unkown protocol version: "
- + protocolVersion);
- }
- }
-
- /**
- * Convert 4 bytes in big-endian representation into a signed int.
- */
- static int bytesToSignedInt(byte[] bytes) {
- if (bytes.length != 4) {
- throw new IllegalArgumentException("Expected 4 bytes to encode int, but got: "
- + bytes.length + " bytes");
- }
-
- return ((bytes[0] << 24) & 0xff000000)
- | ((bytes[1] << 16) & 0x00ff0000)
- | ((bytes[2] << 8) & 0x0000ff00)
- | (bytes[3] & 0x000000ff);
- }
-
- /**
- * Convert a signed int into a 4 byte big-endian representation
- */
- static byte[] signedIntToBytes(int val) {
- byte[] bytes = new byte[4];
-
- bytes[0] = (byte) ((val >> 24) & 0xff);
- bytes[1] = (byte) ((val >> 16) & 0xff);
- bytes[2] = (byte) ((val >> 8) & 0xff);
- bytes[3] = (byte) (val & 0xff);
-
- return bytes;
- }
-}
diff --git a/src/main/java/com/google/security/cryptauth/lib/securegcm/D2DConnectionContextV0.java b/src/main/java/com/google/security/cryptauth/lib/securegcm/D2DConnectionContextV0.java
deleted file mode 100644
index d0efa44..0000000
--- a/src/main/java/com/google/security/cryptauth/lib/securegcm/D2DConnectionContextV0.java
+++ /dev/null
@@ -1,118 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.security.cryptauth.lib.securegcm;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import javax.crypto.SecretKey;
-
-/**
- * Implementation of {@link D2DConnectionContext} for version 0 of the D2D protocol. In this
- * version, communication is half-duplex, as there is a shared key and a shared sequence number
- * between the two sides.
- */
-public class D2DConnectionContextV0 extends D2DConnectionContext {
- public static final int PROTOCOL_VERSION = 0;
-
- private final SecretKey sharedKey;
- private int sequenceNumber;
-
- /**
- * Package private constructor. Should never be called directly except by the
- * {@link D2DHandshakeContext}
- *
- * @param sharedKey
- * @param initialSequenceNumber
- */
- D2DConnectionContextV0(SecretKey sharedKey, int initialSequenceNumber) {
- super(PROTOCOL_VERSION);
- this.sharedKey = sharedKey;
- this.sequenceNumber = initialSequenceNumber;
- }
-
- @Override
- public byte[] getSessionUnique() throws NoSuchAlgorithmException {
- if (sharedKey == null) {
- throw new IllegalStateException(
- "Connection has not been correctly initialized; shared key is null");
- }
-
- MessageDigest md = MessageDigest.getInstance("SHA-256");
- md.update(D2DCryptoOps.SALT);
- return md.digest(sharedKey.getEncoded());
- }
-
- @Override
- protected void incrementSequenceNumberForEncoding() {
- sequenceNumber++;
- }
-
- @Override
- protected void incrementSequenceNumberForDecoding() {
- sequenceNumber++;
- }
-
- @Override
- int getSequenceNumberForEncoding() {
- return sequenceNumber;
- }
-
- @Override
- int getSequenceNumberForDecoding() {
- return sequenceNumber;
- }
-
- @Override
- SecretKey getEncodeKey() {
- return sharedKey;
- }
-
- @Override
- SecretKey getDecodeKey() {
- return sharedKey;
- }
-
- /**
- * Structure of saved session is:
- * +-----------------------------------------------------+
- * | 1 Byte | 4 Bytes (big endian) | 32 Bytes |
- * +-----------------------------------------------------+
- * | Protocol Version | sequence number | key |
- * +-----------------------------------------------------+
- */
- @Override
- public byte[] saveSession() {
- ByteArrayOutputStream bytes = new ByteArrayOutputStream();
-
- try {
- // Protocol version
- bytes.write(0);
-
- // Sequence number
- bytes.write(signedIntToBytes(sequenceNumber));
-
- // Key
- bytes.write(sharedKey.getEncoded());
- } catch (IOException e) {
- // should not happen
- e.printStackTrace();
- return null;
- }
-
- return bytes.toByteArray();
- }
-}
diff --git a/src/main/java/com/google/security/cryptauth/lib/securegcm/D2DConnectionContextV1.java b/src/main/java/com/google/security/cryptauth/lib/securegcm/D2DConnectionContextV1.java
deleted file mode 100644
index 1566849..0000000
--- a/src/main/java/com/google/security/cryptauth/lib/securegcm/D2DConnectionContextV1.java
+++ /dev/null
@@ -1,146 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.security.cryptauth.lib.securegcm;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.Arrays;
-import javax.crypto.SecretKey;
-
-/**
- * Implementation of {@link D2DConnectionContext} for version 1 of the D2D protocol. In this
- * version, communication is fully duplex, as separate keys and sequence nubmers are used for
- * encoding and decoding.
- */
-public class D2DConnectionContextV1 extends D2DConnectionContext {
- public static final int PROTOCOL_VERSION = 1;
-
- private final SecretKey encodeKey;
- private final SecretKey decodeKey;
- private int encodeSequenceNumber;
- private int decodeSequenceNumber;
-
- /**
- * Package private constructor. Should never be called directly except by the
- * {@link D2DHandshakeContext}
- *
- * @param encodeKey
- * @param decodeKey
- * @param initialEncodeSequenceNumber
- * @param initialDecodeSequenceNumber
- */
- D2DConnectionContextV1(
- SecretKey encodeKey,
- SecretKey decodeKey,
- int initialEncodeSequenceNumber,
- int initialDecodeSequenceNumber) {
- super(PROTOCOL_VERSION);
- this.encodeKey = encodeKey;
- this.decodeKey = decodeKey;
- this.encodeSequenceNumber = initialEncodeSequenceNumber;
- this.decodeSequenceNumber = initialDecodeSequenceNumber;
- }
-
- @Override
- public byte[] getSessionUnique() throws NoSuchAlgorithmException {
- if (encodeKey == null || decodeKey == null) {
- throw new IllegalStateException(
- "Connection has not been correctly initialized; encode key or decode key is null");
- }
-
- // Ensure that the initator and responder keys are hashed in a deterministic order, so they have
- // the same session unique code.
- byte[] encodeKeyBytes = encodeKey.getEncoded();
- byte[] decodeKeyBytes = decodeKey.getEncoded();
- int encodeKeyHash = Arrays.hashCode(encodeKeyBytes);
- int decodeKeyHash = Arrays.hashCode(decodeKeyBytes);
- byte[] firstKeyBytes = encodeKeyHash < decodeKeyHash ? encodeKeyBytes : decodeKeyBytes;
- byte[] secondKeyBytes = firstKeyBytes == encodeKeyBytes ? decodeKeyBytes : encodeKeyBytes;
-
- MessageDigest md = MessageDigest.getInstance("SHA-256");
- md.update(D2DCryptoOps.SALT);
- md.update(firstKeyBytes);
- md.update(secondKeyBytes);
- return md.digest();
- }
-
- @Override
- protected void incrementSequenceNumberForEncoding() {
- encodeSequenceNumber++;
- }
-
- @Override
- protected void incrementSequenceNumberForDecoding() {
- decodeSequenceNumber++;
- }
-
- @Override
- int getSequenceNumberForEncoding() {
- return encodeSequenceNumber;
- }
-
- @Override
- int getSequenceNumberForDecoding() {
- return decodeSequenceNumber;
- }
-
- @Override
- SecretKey getEncodeKey() {
- return encodeKey;
- }
-
- @Override
- SecretKey getDecodeKey() {
- return decodeKey;
- }
-
- /**
- * Structure of saved session is:
- * +------------------------------------------------------------------------------------------+
- * | 1 Byte | 4 Bytes (big endian) | 4 Bytes (big endian) | 32 Bytes | 32 Bytes |
- * +------------------------------------------------------------------------------------------+
- * | Protocol Version | encode seq number | decode seq number | encode key | decode key |
- * +------------------------------------------------------------------------------------------+
- */
- @Override
- public byte[] saveSession() {
- ByteArrayOutputStream bytes = new ByteArrayOutputStream();
-
- try {
- // Protocol version
- bytes.write(1);
-
- // Encode sequence number
- bytes.write(signedIntToBytes(encodeSequenceNumber));
-
- // Decode sequence number
- bytes.write(signedIntToBytes(decodeSequenceNumber));
-
- // Encode Key
- bytes.write(encodeKey.getEncoded());
-
- // Decode Key
- bytes.write(decodeKey.getEncoded());
- } catch (IOException e) {
- // should not happen
- e.printStackTrace();
- return null;
- }
-
- return bytes.toByteArray();
- }
-}
diff --git a/src/main/java/com/google/security/cryptauth/lib/securegcm/D2DCryptoOps.java b/src/main/java/com/google/security/cryptauth/lib/securegcm/D2DCryptoOps.java
deleted file mode 100644
index a7203d1..0000000
--- a/src/main/java/com/google/security/cryptauth/lib/securegcm/D2DCryptoOps.java
+++ /dev/null
@@ -1,239 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.security.cryptauth.lib.securegcm;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.security.cryptauth.lib.securegcm.DeviceToDeviceMessagesProto.DeviceToDeviceMessage;
-import com.google.security.cryptauth.lib.securegcm.DeviceToDeviceMessagesProto.ResponderHello;
-import com.google.security.cryptauth.lib.securegcm.SecureGcmProto.GcmMetadata;
-import com.google.security.cryptauth.lib.securegcm.TransportCryptoOps.Payload;
-import com.google.security.cryptauth.lib.securegcm.TransportCryptoOps.PayloadType;
-import com.google.security.cryptauth.lib.securemessage.CryptoOps;
-import com.google.security.cryptauth.lib.securemessage.CryptoOps.EncType;
-import com.google.security.cryptauth.lib.securemessage.CryptoOps.SigType;
-import com.google.security.cryptauth.lib.securemessage.PublicKeyProtoUtil;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageBuilder;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageParser;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.GenericPublicKey;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.Header;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.HeaderAndBody;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.SecureMessage;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.SignatureException;
-import java.security.spec.InvalidKeySpecException;
-import javax.annotation.Nullable;
-import javax.crypto.SecretKey;
-
-/**
- * A collection of static utility methods used by {@link D2DHandshakeContext} for the Device to
- * Device communication (D2D) library.
- */
-class D2DCryptoOps {
- // SHA256 of "D2D"
- // package-private
- static final byte[] SALT = new byte[] {
- (byte) 0x82, (byte) 0xAA, (byte) 0x55, (byte) 0xA0, (byte) 0xD3, (byte) 0x97, (byte) 0xF8,
- (byte) 0x83, (byte) 0x46, (byte) 0xCA, (byte) 0x1C, (byte) 0xEE, (byte) 0x8D, (byte) 0x39,
- (byte) 0x09, (byte) 0xB9, (byte) 0x5F, (byte) 0x13, (byte) 0xFA, (byte) 0x7D, (byte) 0xEB,
- (byte) 0x1D, (byte) 0x4A, (byte) 0xB3, (byte) 0x83, (byte) 0x76, (byte) 0xB8, (byte) 0x25,
- (byte) 0x6D, (byte) 0xA8, (byte) 0x55, (byte) 0x10
- };
-
- // Data passed to hkdf to create the key used by the initiator to encode messages.
- static final String INITIATOR_PURPOSE = "initiator";
- // Data passed to hkdf to create the key used by the responder to encode messages.
- static final String RESPONDER_PURPOSE = "responder";
-
- // Don't instantiate
- private D2DCryptoOps() { }
-
- /**
- * Used by the responder device to create a signcrypted message that contains
- * a payload and a {@link ResponderHello}.
- *
- * @param sharedKey used to signcrypt the {@link Payload}
- * @param publicDhKey the key the recipient will need to derive the shared DH secret.
- * This key will be added to the {@link ResponderHello} in the header.
- * @param protocolVersion the protocol version to include in the proto
- */
- static byte[] signcryptMessageAndResponderHello(
- Payload payload, SecretKey sharedKey, PublicKey publicDhKey, int protocolVersion)
- throws InvalidKeyException, NoSuchAlgorithmException {
- ResponderHello.Builder responderHello = ResponderHello.newBuilder();
- responderHello.setPublicDhKey(PublicKeyProtoUtil.encodePublicKey(publicDhKey));
- responderHello.setProtocolVersion(protocolVersion);
- return signcryptPayload(payload, sharedKey, responderHello.build().toByteArray());
- }
-
- /**
- * Used by a device to send a secure {@link Payload} to another device.
- */
- static byte[] signcryptPayload(
- Payload payload, SecretKey masterKey)
- throws InvalidKeyException, NoSuchAlgorithmException {
- return signcryptPayload(payload, masterKey, null);
- }
-
- /**
- * Used by a device to send a secure {@link Payload} to another device.
- *
- * @param responderHello is an optional public value to attach in the header of
- * the {@link SecureMessage} (in the DecryptionKeyId).
- */
- @VisibleForTesting
- static byte[] signcryptPayload(
- Payload payload, SecretKey masterKey, @Nullable byte[] responderHello)
- throws InvalidKeyException, NoSuchAlgorithmException {
- if ((payload == null) || (masterKey == null)) {
- throw new NullPointerException();
- }
-
- SecureMessageBuilder secureMessageBuilder = new SecureMessageBuilder()
- .setPublicMetadata(GcmMetadata.newBuilder()
- .setType(payload.getPayloadType().getType())
- .setVersion(SecureGcmConstants.SECURE_GCM_VERSION)
- .build()
- .toByteArray());
-
- if (responderHello != null) {
- secureMessageBuilder.setDecryptionKeyId(responderHello);
- }
-
- return secureMessageBuilder.buildSignCryptedMessage(
- masterKey,
- SigType.HMAC_SHA256,
- masterKey,
- EncType.AES_256_CBC,
- payload.getMessage())
- .toByteArray();
- }
-
- /**
- * Extracts a ResponderHello proto from the header of a signcrypted message so that we
- * can derive the shared secret that was used to sign/encrypt the message.
- *
- * @return the {@link ResponderHello} embedded in the signcrypted message.
- */
- static ResponderHello parseAndValidateResponderHello(
- byte[] signcryptedMessageFromResponder) throws InvalidProtocolBufferException {
- if (signcryptedMessageFromResponder == null) {
- throw new NullPointerException();
- }
- SecureMessage secmsg = SecureMessage.parseFrom(signcryptedMessageFromResponder);
- Header messageHeader = SecureMessageParser.getUnverifiedHeader(secmsg);
- if (!messageHeader.hasDecryptionKeyId()) {
- // Maybe this should be a different exception type, because in general, it's legal for the
- // SecureMessage proto to not have the decryption key id, but it's illegal in this protocol.
- throw new InvalidProtocolBufferException("Missing decryption key id");
- }
- byte[] encodedResponderHello = messageHeader.getDecryptionKeyId().toByteArray();
- ResponderHello responderHello = ResponderHello.parseFrom(encodedResponderHello);
- if (!responderHello.hasPublicDhKey()) {
- throw new InvalidProtocolBufferException("Missing public key in responder hello");
- }
- return responderHello;
- }
-
- /**
- * Used by a device to recover a secure {@link Payload} sent by another device.
- */
- static Payload verifydecryptPayload(
- byte[] signcryptedMessage, SecretKey masterKey)
- throws SignatureException, InvalidKeyException, NoSuchAlgorithmException {
- if ((signcryptedMessage == null) || (masterKey == null)) {
- throw new NullPointerException();
- }
- try {
- SecureMessage secmsg = SecureMessage.parseFrom(signcryptedMessage);
- HeaderAndBody parsed = SecureMessageParser.parseSignCryptedMessage(
- secmsg,
- masterKey,
- SigType.HMAC_SHA256,
- masterKey,
- EncType.AES_256_CBC);
- if (!parsed.getHeader().hasPublicMetadata()) {
- throw new SignatureException("missing metadata");
- }
- GcmMetadata metadata = GcmMetadata.parseFrom(parsed.getHeader().getPublicMetadata());
- if (metadata.getVersion() > SecureGcmConstants.SECURE_GCM_VERSION) {
- throw new SignatureException("Unsupported protocol version");
- }
- return new Payload(PayloadType.valueOf(metadata.getType()), parsed.getBody().toByteArray());
- } catch (InvalidProtocolBufferException e) {
- throw new SignatureException(e);
- } catch (IllegalArgumentException e) {
- throw new SignatureException(e);
- }
- }
-
- /**
- * Used by the initiator device to derive the shared key from the {@link PrivateKey} in the
- * {@link D2DHandshakeContext} and the responder's {@link GenericPublicKey} (contained in the
- * {@link ResponderHello} proto).
- */
- static SecretKey deriveSharedKeyFromGenericPublicKey(
- PrivateKey ourPrivateKey, GenericPublicKey theirGenericPublicKey) throws SignatureException {
- try {
- PublicKey theirPublicKey = PublicKeyProtoUtil.parsePublicKey(theirGenericPublicKey);
- return EnrollmentCryptoOps.doKeyAgreement(ourPrivateKey, theirPublicKey);
- } catch (InvalidKeySpecException e) {
- throw new SignatureException(e);
- } catch (InvalidKeyException e) {
- throw new SignatureException(e);
- }
- }
-
- /**
- * Used to derive a distinct key for each initiator and responder.
- *
- * @param masterKey the source key used to derive the new key.
- * @param purpose a string to make the new key different for each purpose.
- * @return the derived {@link SecretKey}.
- */
- static SecretKey deriveNewKeyForPurpose(SecretKey masterKey, String purpose)
- throws NoSuchAlgorithmException, InvalidKeyException {
- byte[] info = purpose.getBytes();
- return KeyEncoding.parseMasterKey(CryptoOps.hkdf(masterKey, SALT, info));
- }
-
- /**
- * Used by the initiator device to decrypt the first payload portion that was sent in the
- * {@code responderHelloAndPayload}, and extract the {@link DeviceToDeviceMessage} contained
- * within it. In order to decrypt, the {@code sharedKey} must first be derived.
- *
- * @see #deriveSharedKeyFromGenericPublicKey(PrivateKey, GenericPublicKey)
- */
- static DeviceToDeviceMessage decryptResponderHelloMessage(
- SecretKey sharedKey, byte[] responderHelloAndPayload) throws SignatureException {
- try {
- Payload payload = verifydecryptPayload(responderHelloAndPayload, sharedKey);
- if (!PayloadType.DEVICE_TO_DEVICE_RESPONDER_HELLO_PAYLOAD.equals(
- payload.getPayloadType())) {
- throw new SignatureException("wrong message type in responder hello");
- }
- return DeviceToDeviceMessage.parseFrom(payload.getMessage());
- } catch (InvalidProtocolBufferException e) {
- throw new SignatureException(e);
- } catch (InvalidKeyException e) {
- throw new SignatureException(e);
- } catch (NoSuchAlgorithmException e) {
- throw new SignatureException(e);
- }
- }
-}
diff --git a/src/main/java/com/google/security/cryptauth/lib/securegcm/D2DDiffieHellmanKeyExchangeHandshake.java b/src/main/java/com/google/security/cryptauth/lib/securegcm/D2DDiffieHellmanKeyExchangeHandshake.java
deleted file mode 100644
index f929a3a..0000000
--- a/src/main/java/com/google/security/cryptauth/lib/securegcm/D2DDiffieHellmanKeyExchangeHandshake.java
+++ /dev/null
@@ -1,307 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.security.cryptauth.lib.securegcm;
-
-import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.security.cryptauth.lib.securegcm.DeviceToDeviceMessagesProto.DeviceToDeviceMessage;
-import com.google.security.cryptauth.lib.securegcm.DeviceToDeviceMessagesProto.InitiatorHello;
-import com.google.security.cryptauth.lib.securegcm.DeviceToDeviceMessagesProto.ResponderHello;
-import com.google.security.cryptauth.lib.securegcm.TransportCryptoOps.Payload;
-import com.google.security.cryptauth.lib.securegcm.TransportCryptoOps.PayloadType;
-import com.google.security.cryptauth.lib.securemessage.PublicKeyProtoUtil;
-import java.security.InvalidKeyException;
-import java.security.KeyPair;
-import java.security.NoSuchAlgorithmException;
-import java.security.PublicKey;
-import java.security.SignatureException;
-import java.security.spec.InvalidKeySpecException;
-import javax.crypto.SecretKey;
-
-/**
- * Implements an unauthenticated EC Diffie Hellman Key Exchange Handshake
- * <p>
- * Initiator sends an InitiatorHello, which is a protobuf that contains a public key. Responder
- * sends a responder hello, which a signed and encrypted message containing a payload, and a public
- * key in the unencrypted header (payload is encrypted with the derived DH key).
- * <p>
- * Example Usage:
- * <pre>
- * // initiator:
- * D2DHandshakeContext initiatorHandshakeContext =
- * D2DDiffieHellmanKeyExchangeHandshake.forInitiator();
- * byte[] initiatorHello = initiatorHandshakeContext.getNextHandshakeMessage();
- * // (send initiatorHello to responder)
- *
- * // responder:
- * D2DHandshakeContext responderHandshakeContext =
- * D2DDiffieHellmanKeyExchangeHandshake.forResponder();
- * responderHandshakeContext.parseHandshakeMessage(initiatorHello);
- * byte[] responderHelloAndPayload = responderHandshakeContext.getNextHandshakeMessage(
- * toBytes(RESPONDER_HELLO_MESSAGE));
- * D2DConnectionContext responderCtx = responderHandshakeContext.toConnectionContext();
- * // (send responderHelloAndPayload to initiator)
- *
- * // initiator
- * byte[] messageFromPayload =
- * initiatorHandshakeContext.parseHandshakeMessage(responderHelloAndPayload);
- * if (messageFromPayload.length > 0) {
- * handle(messageFromPayload);
- * }
- *
- * D2DConnectionContext initiatorCtx = initiatorHandshakeContext.toConnectionContext();
- * </pre>
- */
-public class D2DDiffieHellmanKeyExchangeHandshake implements D2DHandshakeContext {
- private KeyPair ourKeyPair;
- private PublicKey theirPublicKey;
- private SecretKey initiatorEncodeKey;
- private SecretKey responderEncodeKey;
- private State handshakeState;
- private boolean isInitiator;
- private int protocolVersionToUse;
-
- private enum State {
- // Initiator state
- INITIATOR_START,
- INITIATOR_WAITING_FOR_RESPONDER_HELLO,
-
- // Responder state
- RESPONDER_START,
- RESPONDER_AFTER_INITIATOR_HELLO,
-
- // Common completion state
- HANDSHAKE_FINISHED,
- HANDSHAKE_ALREADY_USED
- }
-
- private D2DDiffieHellmanKeyExchangeHandshake(State state) {
- ourKeyPair = PublicKeyProtoUtil.generateEcP256KeyPair();
- theirPublicKey = null;
- initiatorEncodeKey = null;
- responderEncodeKey = null;
- handshakeState = state;
- isInitiator = state == State.INITIATOR_START;
- protocolVersionToUse = D2DConnectionContextV1.PROTOCOL_VERSION;
- }
-
- /**
- * Creates a new Diffie Hellman handshake context for the handshake initiator
- */
- public static D2DDiffieHellmanKeyExchangeHandshake forInitiator() {
- return new D2DDiffieHellmanKeyExchangeHandshake(State.INITIATOR_START);
- }
-
- /**
- * Creates a new Diffie Hellman handshake context for the handshake responder
- */
- public static D2DDiffieHellmanKeyExchangeHandshake forResponder() {
- return new D2DDiffieHellmanKeyExchangeHandshake(State.RESPONDER_START);
- }
-
- @Override
- public boolean isHandshakeComplete() {
- return handshakeState == State.HANDSHAKE_FINISHED
- || handshakeState == State.HANDSHAKE_ALREADY_USED;
- }
-
- @Override
- public byte[] getNextHandshakeMessage() throws HandshakeException {
- switch(handshakeState) {
- case INITIATOR_START:
- handshakeState = State.INITIATOR_WAITING_FOR_RESPONDER_HELLO;
- return InitiatorHello.newBuilder()
- .setPublicDhKey(PublicKeyProtoUtil.encodePublicKey(ourKeyPair.getPublic()))
- .setProtocolVersion(protocolVersionToUse)
- .build()
- .toByteArray();
-
- case RESPONDER_AFTER_INITIATOR_HELLO:
- byte[] responderHello = makeResponderHelloWithPayload(new byte[0]);
- handshakeState = State.HANDSHAKE_FINISHED;
- return responderHello;
-
- default:
- throw new HandshakeException("Cannot get next message in state: " + handshakeState);
- }
- }
-
- @Override
- public boolean canSendPayloadInHandshakeMessage() {
- return handshakeState == State.RESPONDER_AFTER_INITIATOR_HELLO;
- }
-
- @Override
- public byte[] getNextHandshakeMessage(byte[] payload) throws HandshakeException {
- if (handshakeState != State.RESPONDER_AFTER_INITIATOR_HELLO) {
- throw new HandshakeException(
- "Cannot get next message with payload in state: " + handshakeState);
- }
-
- byte[] responderHello = makeResponderHelloWithPayload(payload);
- handshakeState = State.HANDSHAKE_FINISHED;
-
- return responderHello;
- }
-
- private byte[] makeResponderHelloWithPayload(byte[] payload) throws HandshakeException {
- if (payload == null) {
- throw new HandshakeException("Not expecting null payload");
- }
-
- try {
- SecretKey masterKey =
- EnrollmentCryptoOps.doKeyAgreement(ourKeyPair.getPrivate(), theirPublicKey);
-
- // V0 uses the same key for encoding and decoding, but V1 uses separate keys.
- switch (protocolVersionToUse) {
- case D2DConnectionContextV0.PROTOCOL_VERSION:
- initiatorEncodeKey = masterKey;
- responderEncodeKey = masterKey;
- break;
- case D2DConnectionContextV1.PROTOCOL_VERSION:
- initiatorEncodeKey = D2DCryptoOps.deriveNewKeyForPurpose(masterKey,
- D2DCryptoOps.INITIATOR_PURPOSE);
- responderEncodeKey = D2DCryptoOps.deriveNewKeyForPurpose(masterKey,
- D2DCryptoOps.RESPONDER_PURPOSE);
- break;
- default:
- throw new IllegalStateException("Unexpected protocol version: " + protocolVersionToUse);
- }
-
- DeviceToDeviceMessage deviceToDeviceMessage =
- D2DConnectionContext.createDeviceToDeviceMessage(payload, 1 /* sequence number */);
-
- return D2DCryptoOps.signcryptMessageAndResponderHello(
- new Payload(PayloadType.DEVICE_TO_DEVICE_RESPONDER_HELLO_PAYLOAD,
- deviceToDeviceMessage.toByteArray()),
- responderEncodeKey,
- ourKeyPair.getPublic(),
- protocolVersionToUse);
- } catch (InvalidKeyException|NoSuchAlgorithmException e) {
- throw new HandshakeException(e);
- }
- }
-
- @Override
- public byte[] parseHandshakeMessage(byte[] handshakeMessage) throws HandshakeException {
- if (handshakeMessage == null || handshakeMessage.length == 0) {
- throw new HandshakeException("Handshake message too short");
- }
-
- switch(handshakeState) {
- case INITIATOR_WAITING_FOR_RESPONDER_HELLO:
- byte[] payload = parseResponderHello(handshakeMessage);
- handshakeState = State.HANDSHAKE_FINISHED;
- return payload;
-
- case RESPONDER_START:
- parseInitiatorHello(handshakeMessage);
- handshakeState = State.RESPONDER_AFTER_INITIATOR_HELLO;
- return new byte[0];
-
- default:
- throw new HandshakeException("Cannot parse message in state: " + handshakeState);
- }
- }
-
- private byte[] parseResponderHello(byte[] responderHello) throws HandshakeException {
- try {
- ResponderHello responderHelloProto =
- D2DCryptoOps.parseAndValidateResponderHello(responderHello);
-
- // Downgrade to protocol version 0 if needed for backwards compatibility.
- int protocolVersion = responderHelloProto.getProtocolVersion();
- if (protocolVersion == D2DConnectionContextV0.PROTOCOL_VERSION) {
- protocolVersionToUse = D2DConnectionContextV0.PROTOCOL_VERSION;
- }
-
- SecretKey masterKey = D2DCryptoOps.deriveSharedKeyFromGenericPublicKey(
- ourKeyPair.getPrivate(), responderHelloProto.getPublicDhKey());
-
- // V0 uses the same key for encoding and decoding, but V1 uses separate keys.
- if (protocolVersionToUse == D2DConnectionContextV0.PROTOCOL_VERSION) {
- initiatorEncodeKey = masterKey;
- responderEncodeKey = masterKey;
- } else {
- initiatorEncodeKey = D2DCryptoOps.deriveNewKeyForPurpose(masterKey,
- D2DCryptoOps.INITIATOR_PURPOSE);
- responderEncodeKey = D2DCryptoOps.deriveNewKeyForPurpose(masterKey,
- D2DCryptoOps.RESPONDER_PURPOSE);
- }
-
- DeviceToDeviceMessage message =
- D2DCryptoOps.decryptResponderHelloMessage(responderEncodeKey, responderHello);
-
- if (message.getSequenceNumber() != 1) {
- throw new HandshakeException("Incorrect sequence number in responder hello");
- }
-
- return message.getMessage().toByteArray();
- } catch (SignatureException | InvalidProtocolBufferException
- | NoSuchAlgorithmException | InvalidKeyException e) {
- throw new HandshakeException(e);
- }
- }
-
- private void parseInitiatorHello(byte[] initiatorHello) throws HandshakeException {
- try {
- InitiatorHello initiatorHelloProto = InitiatorHello.parseFrom(initiatorHello);
-
- if (!initiatorHelloProto.hasPublicDhKey()) {
- throw new HandshakeException("Missing public key in initiator hello");
- }
-
- theirPublicKey = PublicKeyProtoUtil.parsePublicKey(initiatorHelloProto.getPublicDhKey());
-
- // Downgrade to protocol version 0 if needed for backwards compatibility.
- int protocolVersion = initiatorHelloProto.getProtocolVersion();
- if (protocolVersion == D2DConnectionContextV0.PROTOCOL_VERSION) {
- protocolVersionToUse = D2DConnectionContextV0.PROTOCOL_VERSION;
- }
- } catch (InvalidKeySpecException | InvalidProtocolBufferException e) {
- throw new HandshakeException(e);
- }
- }
-
- @Override
- public D2DConnectionContext toConnectionContext() throws HandshakeException {
- if (handshakeState == State.HANDSHAKE_ALREADY_USED) {
- throw new HandshakeException("Cannot reuse handshake context; is has already been used");
- }
-
- if (!isHandshakeComplete()) {
- throw new HandshakeException("Handshake is not complete; cannot create connection context");
- }
-
- handshakeState = State.HANDSHAKE_ALREADY_USED;
-
- if (protocolVersionToUse == D2DConnectionContextV0.PROTOCOL_VERSION) {
- // Both sides start with an initial sequence number of 1 because the last message of the
- // handshake had an optional payload with sequence number 1. D2DConnectionContext remembers
- // the last sequence number used by each side.
- // Note: initiatorEncodeKey == responderEncodeKey
- return new D2DConnectionContextV0(initiatorEncodeKey, 1 /** initialSequenceNumber */);
- } else {
- SecretKey encodeKey = isInitiator ? initiatorEncodeKey : responderEncodeKey;
- SecretKey decodeKey = isInitiator ? responderEncodeKey : initiatorEncodeKey;
- // Only the responder sends a DeviceToDeviceMessage during the handshake, so it has an initial
- // sequence number of 1. The initiator will therefore have an initial sequence number of 0.
- int initialEncodeSequenceNumber = isInitiator ? 0 : 1;
- int initialDecodeSequenceNumber = isInitiator ? 1 : 0;
- return new D2DConnectionContextV1(
- encodeKey, decodeKey, initialEncodeSequenceNumber, initialDecodeSequenceNumber);
- }
- }
-}
diff --git a/src/main/java/com/google/security/cryptauth/lib/securegcm/D2DHandshakeContext.java b/src/main/java/com/google/security/cryptauth/lib/securegcm/D2DHandshakeContext.java
deleted file mode 100644
index 5fc1d7b..0000000
--- a/src/main/java/com/google/security/cryptauth/lib/securegcm/D2DHandshakeContext.java
+++ /dev/null
@@ -1,158 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.security.cryptauth.lib.securegcm;
-
-/**
- * Describes a cryptographic handshake with arbitrary number of round trips. Some handshake
- * messages may also send a payload.
- *
- * <p>Generic usage for handshake Initiator:
- * {@code
- * // Handshake Initiator
- * D2DHandshakeContext handshake = <specific handshake>.forInitiator();
- * while (!handshake.isHandshakeComplete()) {
- * try {
- * // Get the next handshake message to send
- * byte[] initiatorMessage = handshake.getNextHandshakeMessage();
- *
- * // Send the message out and get the response
- * socket.send(initiatorMessage);
- * byte[] responderMessage = socket.read();
- *
- * // Handle the response and obtain the optional payload
- * byte[] payload = handshake.parseHandshakeMessage(responderMessage);
- *
- * // Handle the payload if one was sent
- * if (payload.length > 0) {
- * handlePayload(payload);
- * }
- * } catch (HandshakeException e) {
- * // Handshake has failed, bail
- * Log("Handshake failed!", e);
- * return;
- * }
- * }
- *
- * ConnectionContext connectionContext;
- * try {
- * // Upgrade handshake context to a full connection context
- * connectionContext = handshake.toConnectionContext();
- * } catch (HandshakeException e) {
- * Log("Cannot convert handshake to connection context", e);
- * }
- * }
- *
- * <p>Generic usage for handshake Responder:
- * {@code
- * // Handshake Responder
- * D2DHandshakeContext handshake = <specific handshake>.forResponder();
- *
- * while (!handshake.isHandshakeComplete()) {
- * try {
- * // Get the message from the initiator
- * byte[] initiatorMessage = socket.read();
- *
- * // Handle the message and get the payload if it exists
- * byte[] payload = handshake.parseHandshakeMessage(initiatorMessage);
- *
- * // Handle the payload if one was sent
- * if (payload.length > 0) {
- * handlePayload(payload);
- * }
- *
- * // Make sure that wasn't the last message
- * if (handshake.isHandshakeComplete()) {
- * break;
- * }
- *
- * // Get next message to send and send it
- * byte[] responderMessage = handshake.getNextHandshakeMessage();
- * socket.send(responderMessage);
- * } catch (HandshakeException e) {
- * // Handshake has failed, bail
- * Log("Handshake failed!", e);
- * return;
- * }
- * }
- *
- * ConnectionContext connectionContext;
- * try {
- * // Upgrade handshake context to a full connection context
- * connectionContext = handshake.toConnectionContext();
- * } catch (HandshakeException e) {
- * Log("Cannot convert handshake to connection context", e);
- * }
- * }
- */
-public interface D2DHandshakeContext {
-
- /**
- * Tells the caller whether the handshake has completed or not. If the handshake is complete, the
- * caller may call {@link #toConnectionContext()} to obtain a connection context.
- *
- * @return true if the handshake is complete, false otherwise
- */
- boolean isHandshakeComplete();
-
- /**
- * Constructs the next message that should be sent in the handshake.
- *
- * @return the next message
- * @throws HandshakeException if the handshake is over or if the next handshake message can't be
- * obtained (e.g., there is an internal error)
- */
- byte[] getNextHandshakeMessage() throws HandshakeException;
-
- /**
- * Tells the caller whether the next handshake message may carry a payload. If true, caller may
- * call {@link #getNextHandshakeMessage(byte[])} instead of the regular
- * {@link #getNextHandshakeMessage()}. If false, calling {@link #getNextHandshakeMessage(byte[])}
- * will result in a {@link HandshakeException}.
- *
- * @return true if the next handshake message can carry a payload, false otherwise
- */
- boolean canSendPayloadInHandshakeMessage();
-
- /**
- * Constructs the next message that should be sent in the handshake along with a payload. Caller
- * should verify that this method can be called by calling
- * {@link #canSendPayloadInHandshakeMessage()}.
- *
- * @param payload the payload to include in the handshake message
- * @return the next message
- * @throws HandshakeException if the handshake is over or if the next handshake message can't be
- * obtained (e.g., there is an internal error) or if the payload may not be included in this
- * message
- */
- byte[] getNextHandshakeMessage(byte[] payload) throws HandshakeException;
-
- /**
- * Parses a handshake message and returns the included payload (if any).
- *
- * @param handshakeMessage message received in the handshake
- * @return payload or empty byte[] if no payload was in the handshake message
- * @throws HandshakeException if an error occurs in parsing the handshake message
- */
- byte[] parseHandshakeMessage(byte[] handshakeMessage) throws HandshakeException;
-
- /**
- * Creates a full {@link D2DConnectionContext}. May only be called if
- * {@link #isHandshakeComplete()} returns true.
- *
- * @return a full {@link D2DConnectionContext}
- * @throws HandshakeException if a connection context cannot be created
- */
- D2DConnectionContext toConnectionContext() throws HandshakeException;
-}
diff --git a/src/main/java/com/google/security/cryptauth/lib/securegcm/Ed25519.java b/src/main/java/com/google/security/cryptauth/lib/securegcm/Ed25519.java
deleted file mode 100644
index 454b942..0000000
--- a/src/main/java/com/google/security/cryptauth/lib/securegcm/Ed25519.java
+++ /dev/null
@@ -1,270 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.security.cryptauth.lib.securegcm;
-
-import static java.math.BigInteger.ONE;
-import static java.math.BigInteger.ZERO;
-
-import com.google.common.annotations.VisibleForTesting;
-import java.math.BigInteger;
-
-/**
- * Implements the Ed25519 twisted Edwards curve. See http://ed25519.cr.yp.to/ for more details.
- */
-public class Ed25519 {
-
- // Don't instantiate
- private Ed25519() { }
-
- // Curve parameters (http://ed25519.cr.yp.to/)
- private static final int HEX_RADIX = 16;
- private static final BigInteger Ed25519_P =
- new BigInteger("7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED", HEX_RADIX);
- private static final BigInteger Ed25519_D =
- new BigInteger("52036CEE2B6FFE738CC740797779E89800700A4D4141D8AB75EB4DCA135978A3", HEX_RADIX);
-
- // Helps to do fast addition k = 2*d
- private static final BigInteger Ed25519_K =
- new BigInteger("2406D9DC56DFFCE7198E80F2EEF3D13000E0149A8283B156EBD69B9426B2F159", HEX_RADIX);
-
- // Identity point in extended representation (0, 1, 1, 0)
- static final BigInteger[] IDENTITY_POINT = new BigInteger[] {ZERO, ONE, ONE, ZERO};
-
- // Helps for reading coordinate type in point representation
- private static final int X = 0;
- private static final int Y = 1;
- private static final int Z = 2;
- private static final int T = 3;
-
- // Number of bits that we need to represent a point. Realistically, we only need 255, but using
- // 256 doesn't hurt.
- private static final int POINT_SIZE_BITS = 256;
-
- /**
- * Returns the result of multiplying point p by scalar k. A point is represented as a BigInteger
- * array of length 2 where the first element (at index 0) is the X coordinate and the second
- * element (at index 1) is the Y coordinate.
- */
- public static BigInteger[] scalarMultiplyAffinePoint(BigInteger[] p, BigInteger k)
- throws Ed25519Exception {
- return toAffine(scalarMultiplyExtendedPoint(toExtended(p), k));
- }
-
- /**
- * Returns the sum of two points in affine representation. A point is represented as a BigInteger
- * array of length 2 where the first element (at index 0) is the X coordinate and the second
- * element (at index 1) is the Y coordinate.
- */
- public static BigInteger[] addAffinePoints(BigInteger[] p1, BigInteger[] p2)
- throws Ed25519Exception {
- return toAffine(addExtendedPoints(toExtended(p1), toExtended(p2)));
- }
-
- /**
- * Returns the result of subtracting p2 from p1 (i.e., p1 - p2) in affine representation. A point
- * is represented as a BigInteger array of length 2 where the first element (at index 0) is the X
- * coordinate and the second element (at index 1) is the Y coordinate.
- */
- public static BigInteger[] subtractAffinePoints(BigInteger[] p1, BigInteger[] p2)
- throws Ed25519Exception {
- return toAffine(subtractExtendedPoints(toExtended(p1), toExtended(p2)));
- }
-
- /**
- * Validates that a given point in affine representation is on the curve and is positive.
- * @throws Ed25519Exception if the point does not validate
- */
- public static void validateAffinePoint(BigInteger[] p) throws Ed25519Exception {
- checkPointIsInAffineRepresentation(p);
-
- BigInteger x = p[X];
- BigInteger y = p[Y];
-
- if (x.signum() != 1 || y.signum() != 1) {
- throw new Ed25519Exception("Point encoding must use only positive integers");
- }
-
- if ((x.compareTo(Ed25519_P) >= 0) || (y.compareTo(Ed25519_P) >= 0)) {
- throw new Ed25519Exception("Point lies outside of the expected field");
- }
-
- BigInteger xx = x.multiply(x);
- BigInteger yy = y.multiply(y);
- BigInteger lhs = xx.negate().add(yy).mod(Ed25519_P); // -x*x + y*y
- BigInteger rhs = ONE.add(Ed25519_D.multiply(xx).multiply(yy)).mod(Ed25519_P); // 1 + d*x*x*y*y
-
- if (!lhs.equals(rhs)) {
- throw new Ed25519Exception("Point does not lie on the expected curve");
- }
- }
-
- /**
- * Returns the result of multiplying point p by scalar k
- */
- static BigInteger[] scalarMultiplyExtendedPoint(BigInteger[] p, BigInteger k)
- throws Ed25519Exception {
- checkPointIsInExtendedRepresentation(p);
- if (k == null) {
- throw new Ed25519Exception("Can't multiply point by null");
- }
-
- if (k.bitLength() > POINT_SIZE_BITS) {
- throw new Ed25519Exception(
- "Refuse to multiply point by scalar with more than " + POINT_SIZE_BITS + " bits");
- }
-
- // Perform best effort time-constant accumulation
- BigInteger[] q = IDENTITY_POINT;
- BigInteger[] r = IDENTITY_POINT;
- BigInteger[] doubleAccumulator = p;
- for (int i = 0; i < POINT_SIZE_BITS; i++) {
- if (k.testBit(i)) {
- q = addExtendedPoints(q, doubleAccumulator);
- } else {
- r = addExtendedPoints(q, doubleAccumulator);
- }
- if (i < POINT_SIZE_BITS - 1) {
- doubleAccumulator = doubleExtendedPoint(doubleAccumulator);
- }
- }
-
- // Not needed, but we're just trying to fool the compiler into not optimizing away r
- r = subtractExtendedPoints(r, r);
- q = addExtendedPoints(q, r);
- return q;
- }
-
- /**
- * Returns the doubling of a point in extended representation
- */
- private static BigInteger[] doubleExtendedPoint(BigInteger[] p) throws Ed25519Exception {
- // The Edwards curve is complete, so we can just add a point to itself.
- // Note that the currently best known algorithms for doubling have the same order as addition.
- // https://hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html
- checkPointIsInExtendedRepresentation(p);
-
- BigInteger c = p[T].pow(2).multiply(Ed25519_K);
- BigInteger d = p[Z].pow(2).shiftLeft(1);
- BigInteger e = p[Y].multiply(p[X]).shiftLeft(2);
- BigInteger f = d.subtract(c);
- BigInteger g = d.add(c);
- BigInteger h = p[Y].pow(2).add(p[X].pow(2)).shiftLeft(1);
-
- return new BigInteger[] {
- e.multiply(f).mod(Ed25519_P),
- g.multiply(h).mod(Ed25519_P),
- f.multiply(g).mod(Ed25519_P),
- e.multiply(h).mod(Ed25519_P)
- };
- }
-
- /**
- * Returns the result of subtracting p2 from p1 (p1 - p2)
- */
- static BigInteger[] subtractExtendedPoints(BigInteger[] p1, BigInteger[] p2)
- throws Ed25519Exception {
- checkPointIsInExtendedRepresentation(p1);
- checkPointIsInExtendedRepresentation(p2);
-
- return addExtendedPoints(p1, new BigInteger[] {p2[X].negate(), p2[Y], p2[Z], p2[T].negate()});
- }
-
- /**
- * Returns the sum of two points in extended representation
- * Uses: https://hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html#addition-add-2008-hwcd-3
- */
- static BigInteger[] addExtendedPoints(BigInteger[] p1, BigInteger[] p2)
- throws Ed25519Exception {
- checkPointIsInExtendedRepresentation(p1);
- checkPointIsInExtendedRepresentation(p2);
-
- BigInteger a = p1[Y].subtract(p1[X]).multiply(p2[Y].subtract(p2[X]));
- BigInteger b = p1[Y].add(p1[X]).multiply(p2[Y].add(p2[X]));
- BigInteger c = p1[T].multiply(Ed25519_K).multiply(p2[T]);
- BigInteger d = p1[Z].add(p1[Z]).multiply(p2[Z]);
- BigInteger e = b.subtract(a);
- BigInteger f = d.subtract(c);
- BigInteger g = d.add(c);
- BigInteger h = b.add(a);
-
- return new BigInteger[] {
- e.multiply(f).mod(Ed25519_P),
- g.multiply(h).mod(Ed25519_P),
- f.multiply(g).mod(Ed25519_P),
- e.multiply(h).mod(Ed25519_P)
- };
- }
-
- /** Converts a point in affine representation to extended representation */
- // TODO(b/120887495): This @VisibleForTesting annotation was being ignored by prod code.
- // Please check that removing it is correct, and remove this comment along with it.
- // @VisibleForTesting
- static BigInteger[] toExtended(BigInteger[] p) throws Ed25519Exception {
- checkPointIsInAffineRepresentation(p);
-
- return new BigInteger[] {p[X], p[Y], ONE, p[X].multiply(p[Y]).mod(Ed25519_P)}; // x, y, 1, x*y
- }
-
- /** Converts a point in extended representation to affine representation */
- // TODO(b/120887495): This @VisibleForTesting annotation was being ignored by prod code.
- // Please check that removing it is correct, and remove this comment along with it.
- // @VisibleForTesting
- static BigInteger[] toAffine(BigInteger[] p) throws Ed25519Exception {
- checkPointIsInExtendedRepresentation(p);
-
- return new BigInteger[] {p[X].multiply(p[Z].modInverse(Ed25519_P)).mod(Ed25519_P), // x = X / Z
- p[Y].multiply(p[Z].modInverse(Ed25519_P)).mod(Ed25519_P)}; // y = Y / Z
- }
-
- /**
- * Checks that a given point is in the extended representation
- * @throws Ed25519Exception if the point is not in the extended representation
- */
- @VisibleForTesting
- static void checkPointIsInExtendedRepresentation(BigInteger[] p) throws Ed25519Exception {
- if (p == null || p.length != 4 || p[X] == null || p[Y] == null || p[Z] == null
- || p[T] == null) {
- throw new Ed25519Exception("Point is not in extended representation");
- }
- }
-
- /**
- * Checks that a given point is in the affine representation
- * @throws Ed25519Exception if the point is not in the affine representation
- */
- @VisibleForTesting
- static void checkPointIsInAffineRepresentation(BigInteger[] p) throws Ed25519Exception {
- if (p == null || p.length != 2 || p[X] == null || p[Y] == null) {
- throw new Ed25519Exception("Point is not in affine representation");
- }
- }
-
- /**
- * Represents an unrecoverable error that has occurred while performing a curve operation.
- */
- public static class Ed25519Exception extends Exception {
- public Ed25519Exception(String message) {
- super(message);
- }
-
- public Ed25519Exception(Exception e) {
- super(e);
- }
-
- public Ed25519Exception(String message, Exception e) {
- super(message, e);
- }
- }
-}
diff --git a/src/main/java/com/google/security/cryptauth/lib/securegcm/EnrollmentCryptoOps.java b/src/main/java/com/google/security/cryptauth/lib/securegcm/EnrollmentCryptoOps.java
deleted file mode 100644
index 450c806..0000000
--- a/src/main/java/com/google/security/cryptauth/lib/securegcm/EnrollmentCryptoOps.java
+++ /dev/null
@@ -1,233 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.security.cryptauth.lib.securegcm;
-
-import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.security.annotations.SuppressInsecureCipherModeCheckerPendingReview;
-import com.google.security.cryptauth.lib.securegcm.SecureGcmProto.GcmDeviceInfo;
-import com.google.security.cryptauth.lib.securegcm.SecureGcmProto.GcmMetadata;
-import com.google.security.cryptauth.lib.securegcm.TransportCryptoOps.PayloadType;
-import com.google.security.cryptauth.lib.securemessage.CryptoOps.EncType;
-import com.google.security.cryptauth.lib.securemessage.CryptoOps.SigType;
-import com.google.security.cryptauth.lib.securemessage.PublicKeyProtoUtil;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageBuilder;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageParser;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.HeaderAndBody;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.SecureMessage;
-import java.security.InvalidKeyException;
-import java.security.KeyPair;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.SignatureException;
-import java.security.spec.InvalidKeySpecException;
-import java.util.Arrays;
-import javax.crypto.KeyAgreement;
-import javax.crypto.SecretKey;
-
-/**
- * Utility class for implementing Secure GCM enrollment flows.
- */
-public class EnrollmentCryptoOps {
-
- private EnrollmentCryptoOps() { } // Do not instantiate
-
- /**
- * Type of symmetric key signature to use for the signcrypted "outer layer" message.
- */
- private static final SigType OUTER_SIG_TYPE = SigType.HMAC_SHA256;
-
- /**
- * Type of symmetric key encryption to use for the signcrypted "outer layer" message.
- */
- private static final EncType OUTER_ENC_TYPE = EncType.AES_256_CBC;
-
- /**
- * Type of public key signature to use for the (cleartext) "inner layer" message.
- */
- private static final SigType INNER_SIG_TYPE = SigType.ECDSA_P256_SHA256;
-
- /**
- * Type of public key signature to use for the (cleartext) "inner layer" message on platforms that
- * don't support Elliptic Curve operations (such as old Android versions).
- */
- private static final SigType LEGACY_INNER_SIG_TYPE = SigType.RSA2048_SHA256;
-
- /**
- * Which {@link KeyAgreement} algorithm to use.
- */
- private static final String KA_ALG = "ECDH";
-
- /**
- * Which {@link KeyAgreement} algorithm to use on platforms that don't support Elliptic Curve.
- */
- private static final String LEGACY_KA_ALG = "DH";
-
- /**
- * Used by both the client and server to perform a key exchange.
- *
- * @return a {@link SecretKey} derived from the key exchange
- * @throws InvalidKeyException if either of the input keys is of the wrong type
- */
- @SuppressInsecureCipherModeCheckerPendingReview // b/32143855
- public static SecretKey doKeyAgreement(PrivateKey myKey, PublicKey peerKey)
- throws InvalidKeyException {
- String alg = KA_ALG;
- if (KeyEncoding.isLegacyPrivateKey(myKey)) {
- alg = LEGACY_KA_ALG;
- }
- KeyAgreement agreement;
- try {
- agreement = KeyAgreement.getInstance(alg);
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException(e);
- }
-
- agreement.init(myKey);
- agreement.doPhase(peerKey, true);
- byte[] agreedKey = agreement.generateSecret();
-
- // Derive a 256-bit AES key by using sha256 on the Diffie-Hellman output
- return KeyEncoding.parseMasterKey(sha256(agreedKey));
- }
-
- public static KeyPair generateEnrollmentKeyAgreementKeyPair(boolean isLegacy) {
- if (isLegacy) {
- return PublicKeyProtoUtil.generateDh2048KeyPair();
- }
- return PublicKeyProtoUtil.generateEcP256KeyPair();
- }
-
- /**
- * @return SHA-256 hash of {@code masterKey}
- */
- public static byte[] getMasterKeyHash(SecretKey masterKey) {
- return sha256(masterKey.getEncoded());
- }
-
- /**
- * Used by the client to signcrypt an enrollment request before sending it to the server.
- *
- * <p>Note: You <em>MUST</em> correctly set the value of the {@code device_master_key_hash} on
- * {@code enrollmentInfo} from {@link #getMasterKeyHash(SecretKey)} before calling this method.
- *
- * @param enrollmentInfo the enrollment request to send to the server. You must correctly set
- * the {@code device_master_key_hash} field.
- * @param masterKey the shared key derived from the key agreement
- * @param signingKey the signing key corresponding to the user's {@link PublicKey} being enrolled
- * @return the encrypted enrollment message
- * @throws IllegalArgumentException if {@code enrollmentInfo} doesn't have a valid
- * {@code device_master_key_hash}
- * @throws InvalidKeyException if {@code masterKey} or {@code signingKey} is the wrong type
- */
- public static byte[] encryptEnrollmentMessage(
- GcmDeviceInfo enrollmentInfo, SecretKey masterKey, PrivateKey signingKey)
- throws InvalidKeyException, NoSuchAlgorithmException {
- if ((enrollmentInfo == null) || (masterKey == null) || (signingKey == null)) {
- throw new NullPointerException();
- }
-
- if (!Arrays.equals(enrollmentInfo.getDeviceMasterKeyHash().toByteArray(),
- getMasterKeyHash(masterKey))) {
- throw new IllegalArgumentException("DeviceMasterKeyHash not set correctly");
- }
-
- // First create the inner message, which is basically a self-signed certificate
- SigType sigType =
- KeyEncoding.isLegacyPrivateKey(signingKey) ? LEGACY_INNER_SIG_TYPE : INNER_SIG_TYPE;
- SecureMessage innerMsg = new SecureMessageBuilder()
- .setVerificationKeyId(enrollmentInfo.getUserPublicKey().toByteArray())
- .buildSignedCleartextMessage(signingKey, sigType, enrollmentInfo.toByteArray());
-
- // Next create the outer message, which uses the newly exchanged master key to signcrypt
- SecureMessage outerMsg = new SecureMessageBuilder()
- .setVerificationKeyId(new byte[] {}) // Empty
- .setPublicMetadata(GcmMetadata.newBuilder()
- .setType(PayloadType.ENROLLMENT.getType())
- .setVersion(SecureGcmConstants.SECURE_GCM_VERSION)
- .build()
- .toByteArray())
- .buildSignCryptedMessage(
- masterKey, OUTER_SIG_TYPE, masterKey, OUTER_ENC_TYPE, innerMsg.toByteArray());
- return outerMsg.toByteArray();
- }
-
- /**
- * Used by the server to decrypt the client's enrollment request.
- * @param enrollmentMessage generated by the client's call to
- * {@link #encryptEnrollmentMessage(GcmDeviceInfo, SecretKey, PrivateKey)}
- * @param masterKey the shared key derived from the key agreement
- * @return the client's enrollment request data
- * @throws SignatureException if {@code enrollmentMessage} is malformed or has been tampered with
- * @throws InvalidKeyException if {@code masterKey} is the wrong type
- */
- public static GcmDeviceInfo decryptEnrollmentMessage(
- byte[] enrollmentMessage, SecretKey masterKey, boolean isLegacy)
- throws SignatureException, InvalidKeyException, NoSuchAlgorithmException {
- if ((enrollmentMessage == null) || (masterKey == null)) {
- throw new NullPointerException();
- }
-
- HeaderAndBody outerHeaderAndBody;
- GcmMetadata outerMetadata;
- HeaderAndBody innerHeaderAndBody;
- byte[] encodedUserPublicKey;
- GcmDeviceInfo enrollmentInfo;
- try {
- SecureMessage outerMsg = SecureMessage.parseFrom(enrollmentMessage);
- outerHeaderAndBody = SecureMessageParser.parseSignCryptedMessage(
- outerMsg, masterKey, OUTER_SIG_TYPE, masterKey, OUTER_ENC_TYPE);
- outerMetadata = GcmMetadata.parseFrom(outerHeaderAndBody.getHeader().getPublicMetadata());
-
- SecureMessage innerMsg = SecureMessage.parseFrom(outerHeaderAndBody.getBody());
- encodedUserPublicKey = SecureMessageParser.getUnverifiedHeader(innerMsg)
- .getVerificationKeyId().toByteArray();
- PublicKey userPublicKey = KeyEncoding.parseUserPublicKey(encodedUserPublicKey);
- SigType sigType = isLegacy ? LEGACY_INNER_SIG_TYPE : INNER_SIG_TYPE;
- innerHeaderAndBody = SecureMessageParser.parseSignedCleartextMessage(
- innerMsg, userPublicKey, sigType);
- enrollmentInfo = GcmDeviceInfo.parseFrom(innerHeaderAndBody.getBody());
- } catch (InvalidProtocolBufferException e) {
- throw new SignatureException(e);
- } catch (InvalidKeySpecException e) {
- throw new SignatureException(e);
- }
-
- boolean verified =
- (outerMetadata.getType() == PayloadType.ENROLLMENT.getType())
- && (outerMetadata.getVersion() <= SecureGcmConstants.SECURE_GCM_VERSION)
- && outerHeaderAndBody.getHeader().getVerificationKeyId().isEmpty()
- && innerHeaderAndBody.getHeader().getPublicMetadata().isEmpty()
- // Verify the encoded public key we used matches the encoded public key key being enrolled
- && Arrays.equals(encodedUserPublicKey, enrollmentInfo.getUserPublicKey().toByteArray())
- && Arrays.equals(getMasterKeyHash(masterKey),
- enrollmentInfo.getDeviceMasterKeyHash().toByteArray());
-
- if (verified) {
- return enrollmentInfo;
- }
- throw new SignatureException();
- }
-
- static byte[] sha256(byte[] input) {
- try {
- MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
- return sha256.digest(input);
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException(e); // Shouldn't happen
- }
- }
-}
diff --git a/src/main/java/com/google/security/cryptauth/lib/securegcm/HandshakeException.java b/src/main/java/com/google/security/cryptauth/lib/securegcm/HandshakeException.java
deleted file mode 100644
index b717eb6..0000000
--- a/src/main/java/com/google/security/cryptauth/lib/securegcm/HandshakeException.java
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.security.cryptauth.lib.securegcm;
-
-/**
- * Represents an unrecoverable error that has occurred during the handshake procedure.
- */
-public class HandshakeException extends Exception {
- public HandshakeException(String message) {
- super(message);
- }
-
- public HandshakeException(Exception e) {
- super(e);
- }
-
- public HandshakeException(String message, Exception e) {
- super(message, e);
- }
-}
diff --git a/src/main/java/com/google/security/cryptauth/lib/securegcm/KeyEncoding.java b/src/main/java/com/google/security/cryptauth/lib/securegcm/KeyEncoding.java
deleted file mode 100644
index 67e4ace..0000000
--- a/src/main/java/com/google/security/cryptauth/lib/securegcm/KeyEncoding.java
+++ /dev/null
@@ -1,180 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.security.cryptauth.lib.securegcm;
-
-import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.security.cryptauth.lib.securemessage.PublicKeyProtoUtil;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.GenericPublicKey;
-import java.security.KeyFactory;
-import java.security.NoSuchAlgorithmException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.interfaces.ECPrivateKey;
-import java.security.interfaces.ECPublicKey;
-import java.security.spec.InvalidKeySpecException;
-import java.security.spec.PKCS8EncodedKeySpec;
-import javax.crypto.SecretKey;
-import javax.crypto.interfaces.DHPrivateKey;
-import javax.crypto.spec.SecretKeySpec;
-
-/**
- * Utility class for encoding and parsing keys used by SecureGcm.
- */
-public class KeyEncoding {
- private KeyEncoding() {} // Do not instantiate
-
- private static boolean simulateLegacyCryptoRequired = false;
-
- /**
- * The JCA algorithm name to use when encoding/decoding symmetric keys.
- */
- static final String SYMMETRIC_KEY_ENCODING_ALG = "AES";
-
- public static byte[] encodeMasterKey(SecretKey masterKey) {
- return masterKey.getEncoded();
- }
-
- public static SecretKey parseMasterKey(byte[] encodedMasterKey) {
- return new SecretKeySpec(encodedMasterKey, SYMMETRIC_KEY_ENCODING_ALG);
- }
-
- public static byte[] encodeUserPublicKey(PublicKey pk) {
- return encodePublicKey(pk);
- }
-
- public static byte[] encodeUserPrivateKey(PrivateKey sk) {
- return sk.getEncoded();
- }
-
- public static byte[] encodeDeviceSyncGroupPublicKey(PublicKey pk) {
- return PublicKeyProtoUtil.encodePaddedEcPublicKey(pk).toByteArray();
- }
-
- public static PrivateKey parseUserPrivateKey(byte[] encodedPrivateKey, boolean isLegacy)
- throws InvalidKeySpecException {
- PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encodedPrivateKey);
- if (isLegacy) {
- return getRsaKeyFactory().generatePrivate(keySpec);
- }
- return getEcKeyFactory().generatePrivate(keySpec);
- }
-
- public static PublicKey parseUserPublicKey(byte[] keyBytes) throws InvalidKeySpecException {
- return parsePublicKey(keyBytes);
- }
-
- public static PublicKey parseDeviceSyncGroupPublicKey(byte[] keyBytes)
- throws InvalidKeySpecException {
- return parsePublicKey(keyBytes);
- }
-
- public static byte[] encodeKeyAgreementPublicKey(PublicKey pk) {
- return encodePublicKey(pk);
- }
-
- public static PublicKey parseKeyAgreementPublicKey(byte[] keyBytes)
- throws InvalidKeySpecException {
- return parsePublicKey(keyBytes);
- }
-
- public static byte[] encodeKeyAgreementPrivateKey(PrivateKey sk) {
- if (isLegacyPrivateKey(sk)) {
- return PublicKeyProtoUtil.encodeDh2048PrivateKey((DHPrivateKey) sk);
- }
- return sk.getEncoded();
- }
-
- public static PrivateKey parseKeyAgreementPrivateKey(byte[] keyBytes, boolean isLegacy)
- throws InvalidKeySpecException {
- if (isLegacy) {
- return PublicKeyProtoUtil.parseDh2048PrivateKey(keyBytes);
- }
- PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
- return getEcKeyFactory().generatePrivate(keySpec);
- }
-
- public static byte[] encodeSigningPublicKey(PublicKey pk) {
- return encodePublicKey(pk);
- }
-
- public static PublicKey parseSigningPublicKey(byte[] keyBytes) throws InvalidKeySpecException {
- return parsePublicKey(keyBytes);
- }
-
- public static byte[] encodeSigningPrivateKey(PrivateKey sk) {
- return sk.getEncoded();
- }
-
- public static PrivateKey parseSigningPrivateKey(byte[] keyBytes) throws InvalidKeySpecException {
- PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
- return getEcKeyFactory().generatePrivate(keySpec);
- }
-
- public static boolean isLegacyPublicKey(PublicKey pk) {
- if (pk instanceof ECPublicKey) {
- return false;
- }
- return true;
- }
-
- public static boolean isLegacyPrivateKey(PrivateKey sk) {
- if (sk instanceof ECPrivateKey) {
- return false;
- }
- return true;
- }
-
- public static boolean isLegacyCryptoRequired() {
- return PublicKeyProtoUtil.isLegacyCryptoRequired() || simulateLegacyCryptoRequired;
- }
-
- /**
- * When testing, use this to force {@link #isLegacyCryptoRequired()} to return {@code true}
- */
- // @VisibleForTesting
- public static void setSimulateLegacyCrypto(boolean forceLegacy) {
- simulateLegacyCryptoRequired = forceLegacy;
- }
-
- private static byte[] encodePublicKey(PublicKey pk) {
- return PublicKeyProtoUtil.encodePublicKey(pk).toByteArray();
- }
-
- private static PublicKey parsePublicKey(byte[] keyBytes) throws InvalidKeySpecException {
- try {
- return PublicKeyProtoUtil.parsePublicKey(GenericPublicKey.parseFrom(keyBytes));
- } catch (InvalidProtocolBufferException e) {
- throw new InvalidKeySpecException("Unable to parse GenericPublicKey", e);
- } catch (IllegalArgumentException e) {
- throw new InvalidKeySpecException("Unable to parse GenericPublicKey", e);
- }
- }
-
- static KeyFactory getEcKeyFactory() {
- try {
- return KeyFactory.getInstance("EC");
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException(e); // No ECDH provider available
- }
- }
-
- static KeyFactory getRsaKeyFactory() {
- try {
- return KeyFactory.getInstance("RSA");
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException(e); // No RSA provider available
- }
- }
-}
diff --git a/src/main/java/com/google/security/cryptauth/lib/securegcm/SecureGcmConstants.java b/src/main/java/com/google/security/cryptauth/lib/securegcm/SecureGcmConstants.java
deleted file mode 100644
index a69431f..0000000
--- a/src/main/java/com/google/security/cryptauth/lib/securegcm/SecureGcmConstants.java
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.security.cryptauth.lib.securegcm;
-
-/**
- * A container for GCM related constants used by SecureGcm channels.
- */
-public final class SecureGcmConstants {
- private SecureGcmConstants() {} // Do not instantiate
-
- public static final int SECURE_GCM_VERSION = 1;
-
- /**
- * The GCM sender identity used by this library (GMSCore).
- */
- public static final String SENDER_ID = "745476177629";
-
- /**
- * The key used for indexing the GCM {@link TransportCryptoOps.Payload} within {@code AppData}.
- */
- public static final String MESSAGE_KEY = "P";
-
- /**
- * The origin that should be use for GCM device enrollments.
- */
- public static final String GOOGLE_ORIGIN = "google.com";
-
- /**
- * The origin that should be use for GCM Legacy android device enrollments.
- */
- public static final String LEGACY_ANDROID_ORIGIN = "c.g.a.gms";
-
- /**
- * The name of the protocol this library speaks.
- */
- public static final String PROTOCOL_TYPE_NAME = "gcmV1";
-}
diff --git a/src/main/java/com/google/security/cryptauth/lib/securegcm/TransportCryptoOps.java b/src/main/java/com/google/security/cryptauth/lib/securegcm/TransportCryptoOps.java
deleted file mode 100644
index b053bf4..0000000
--- a/src/main/java/com/google/security/cryptauth/lib/securegcm/TransportCryptoOps.java
+++ /dev/null
@@ -1,268 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.security.cryptauth.lib.securegcm;
-
-import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.security.cryptauth.lib.securegcm.SecureGcmProto.GcmMetadata;
-import com.google.security.cryptauth.lib.securemessage.CryptoOps.EncType;
-import com.google.security.cryptauth.lib.securemessage.CryptoOps.SigType;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageBuilder;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageParser;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.HeaderAndBody;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.SecureMessage;
-import java.security.InvalidKeyException;
-import java.security.KeyPair;
-import java.security.NoSuchAlgorithmException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.SignatureException;
-import java.security.interfaces.ECPublicKey;
-import java.security.interfaces.RSAPublicKey;
-import javax.crypto.SecretKey;
-
-/**
- * Utility class for implementing a secure transport for GCM messages.
- */
-public class TransportCryptoOps {
- private TransportCryptoOps() {} // Do not instantiate
-
- /**
- * A type safe version of the {@link SecureGcmProto} {@code Type} codes.
- */
- public enum PayloadType {
- ENROLLMENT(SecureGcmProto.Type.ENROLLMENT),
- TICKLE(SecureGcmProto.Type.TICKLE),
- TX_REQUEST(SecureGcmProto.Type.TX_REQUEST),
- TX_REPLY(SecureGcmProto.Type.TX_REPLY),
- TX_SYNC_REQUEST(SecureGcmProto.Type.TX_SYNC_REQUEST),
- TX_SYNC_RESPONSE(SecureGcmProto.Type.TX_SYNC_RESPONSE),
- TX_PING(SecureGcmProto.Type.TX_PING),
- DEVICE_INFO_UPDATE(SecureGcmProto.Type.DEVICE_INFO_UPDATE),
- TX_CANCEL_REQUEST(SecureGcmProto.Type.TX_CANCEL_REQUEST),
- LOGIN_NOTIFICATION(SecureGcmProto.Type.LOGIN_NOTIFICATION),
- PROXIMITYAUTH_PAIRING(SecureGcmProto.Type.PROXIMITYAUTH_PAIRING),
- GCMV1_IDENTITY_ASSERTION(SecureGcmProto.Type.GCMV1_IDENTITY_ASSERTION),
- DEVICE_TO_DEVICE_RESPONDER_HELLO_PAYLOAD(
- SecureGcmProto.Type.DEVICE_TO_DEVICE_RESPONDER_HELLO_PAYLOAD),
- DEVICE_TO_DEVICE_MESSAGE(SecureGcmProto.Type.DEVICE_TO_DEVICE_MESSAGE),
- DEVICE_PROXIMITY_CALLBACK(SecureGcmProto.Type.DEVICE_PROXIMITY_CALLBACK),
- UNLOCK_KEY_SIGNED_CHALLENGE(SecureGcmProto.Type.UNLOCK_KEY_SIGNED_CHALLENGE);
-
- private final SecureGcmProto.Type type;
- PayloadType(SecureGcmProto.Type type) {
- this.type = type;
- }
-
- public SecureGcmProto.Type getType() {
- return this.type;
- }
-
- public static PayloadType valueOf(SecureGcmProto.Type type) {
- return PayloadType.valueOf(type.getNumber());
- }
-
- public static PayloadType valueOf(int type) {
- for (PayloadType payloadType : PayloadType.values()) {
- if (payloadType.getType().getNumber() == type) {
- return payloadType;
- }
- }
- throw new IllegalArgumentException("Unsupported payload type: " + type);
- }
- }
-
- /**
- * Encapsulates a {@link PayloadType} specifier, and a corresponding raw {@code message} payload.
- */
- public static class Payload {
- private final PayloadType payloadType;
- private final byte[] message;
-
- public Payload(PayloadType payloadType, byte[] message) {
- if ((payloadType == null) || (message == null)) {
- throw new NullPointerException();
- }
- this.payloadType = payloadType;
- this.message = message;
- }
-
- public PayloadType getPayloadType() {
- return payloadType;
- }
-
- public byte[] getMessage() {
- return message;
- }
- }
-
- /**
- * Used by the the server-side to send a secure {@link Payload} to the client.
- *
- * @param masterKey used to signcrypt the {@link Payload}
- * @param keyHandle the name by which the client refers to the specified {@code masterKey}
- */
- public static byte[] signcryptServerMessage(
- Payload payload, SecretKey masterKey, byte[] keyHandle)
- throws InvalidKeyException, NoSuchAlgorithmException {
- if ((payload == null) || (masterKey == null) || (keyHandle == null)) {
- throw new NullPointerException();
- }
- return new SecureMessageBuilder()
- .setVerificationKeyId(keyHandle)
- .setPublicMetadata(GcmMetadata.newBuilder()
- .setType(payload.getPayloadType().getType())
- .setVersion(SecureGcmConstants.SECURE_GCM_VERSION)
- .build()
- .toByteArray())
- .buildSignCryptedMessage(
- masterKey,
- SigType.HMAC_SHA256,
- masterKey,
- EncType.AES_256_CBC,
- payload.getMessage())
- .toByteArray();
- }
-
- /**
- * Extracts the {@code keyHandle} from a {@code signcryptedMessage}.
- *
- * @see #signcryptServerMessage(Payload, SecretKey, byte[])
- */
- public static byte[] getKeyHandleFor(byte[] signcryptedServerMessage)
- throws InvalidProtocolBufferException {
- if (signcryptedServerMessage == null) {
- throw new NullPointerException();
- }
- SecureMessage secmsg = SecureMessage.parseFrom(signcryptedServerMessage);
- return SecureMessageParser.getUnverifiedHeader(secmsg).getVerificationKeyId().toByteArray();
- }
-
- /**
- * Used by a client to recover a secure {@link Payload} sent by the server-side.
- *
- * @see #getKeyHandleFor(byte[])
- * @see #signcryptServerMessage(Payload, SecretKey, byte[])
- */
- public static Payload verifydecryptServerMessage(
- byte[] signcryptedServerMessage, SecretKey masterKey)
- throws SignatureException, InvalidKeyException, NoSuchAlgorithmException {
- if ((signcryptedServerMessage == null) || (masterKey == null)) {
- throw new NullPointerException();
- }
- try {
- SecureMessage secmsg = SecureMessage.parseFrom(signcryptedServerMessage);
- HeaderAndBody parsed = SecureMessageParser.parseSignCryptedMessage(
- secmsg,
- masterKey,
- SigType.HMAC_SHA256,
- masterKey,
- EncType.AES_256_CBC);
- GcmMetadata metadata = GcmMetadata.parseFrom(parsed.getHeader().getPublicMetadata());
- if (metadata.getVersion() > SecureGcmConstants.SECURE_GCM_VERSION) {
- throw new SignatureException("Unsupported protocol version");
- }
- return new Payload(PayloadType.valueOf(metadata.getType()), parsed.getBody().toByteArray());
- } catch (InvalidProtocolBufferException | IllegalArgumentException e) {
- throw new SignatureException(e);
- }
- }
-
- /**
- * Used by the the client-side to send a secure {@link Payload} to the client.
- *
- * @param userKeyPair used to sign the {@link Payload}. In particular, the {@link PrivateKey}
- * portion is used for signing, and (the {@link PublicKey} portion is sent to the server.
- * @param masterKey used to encrypt the {@link Payload}
- */
- public static byte[] signcryptClientMessage(
- Payload payload, KeyPair userKeyPair, SecretKey masterKey)
- throws InvalidKeyException, NoSuchAlgorithmException {
- if ((payload == null) || (masterKey == null)) {
- throw new NullPointerException();
- }
-
- PublicKey userPublicKey = userKeyPair.getPublic();
- PrivateKey userPrivateKey = userKeyPair.getPrivate();
-
- return new SecureMessageBuilder()
- .setVerificationKeyId(KeyEncoding.encodeUserPublicKey(userPublicKey))
- .setPublicMetadata(GcmMetadata.newBuilder()
- .setType(payload.getPayloadType().getType())
- .setVersion(SecureGcmConstants.SECURE_GCM_VERSION)
- .build()
- .toByteArray())
- .buildSignCryptedMessage(
- userPrivateKey,
- getSigTypeFor(userPublicKey),
- masterKey,
- EncType.AES_256_CBC,
- payload.getMessage())
- .toByteArray();
- }
-
- /**
- * Used by the server-side to recover a secure {@link Payload} sent by a client.
- *
- * @see #getEncodedUserPublicKeyFor(byte[])
- * @see #signcryptClientMessage(Payload, KeyPair, SecretKey)
- */
- public static Payload verifydecryptClientMessage(
- byte[] signcryptedClientMessage, PublicKey userPublicKey, SecretKey masterKey)
- throws SignatureException, InvalidKeyException, NoSuchAlgorithmException {
- if ((signcryptedClientMessage == null) || (masterKey == null)) {
- throw new NullPointerException();
- }
- try {
- SecureMessage secmsg = SecureMessage.parseFrom(signcryptedClientMessage);
- HeaderAndBody parsed = SecureMessageParser.parseSignCryptedMessage(
- secmsg,
- userPublicKey,
- getSigTypeFor(userPublicKey),
- masterKey,
- EncType.AES_256_CBC);
- GcmMetadata metadata = GcmMetadata.parseFrom(parsed.getHeader().getPublicMetadata());
- if (metadata.getVersion() > SecureGcmConstants.SECURE_GCM_VERSION) {
- throw new SignatureException("Unsupported protocol version");
- }
- return new Payload(PayloadType.valueOf(metadata.getType()), parsed.getBody().toByteArray());
- } catch (InvalidProtocolBufferException | IllegalArgumentException e) {
- throw new SignatureException(e);
- }
- }
-
- /**
- * Extracts an encoded {@code userPublicKey} from a {@code signcryptedClientMessage}.
- *
- * @see #signcryptClientMessage(Payload, KeyPair, SecretKey)
- */
- public static byte[] getEncodedUserPublicKeyFor(byte[] signcryptedClientMessage)
- throws InvalidProtocolBufferException {
- if (signcryptedClientMessage == null) {
- throw new NullPointerException();
- }
- SecureMessage secmsg = SecureMessage.parseFrom(signcryptedClientMessage);
- return SecureMessageParser.getUnverifiedHeader(secmsg).getVerificationKeyId().toByteArray();
- }
-
- private static SigType getSigTypeFor(PublicKey userPublicKey) throws InvalidKeyException {
- if (userPublicKey instanceof ECPublicKey) {
- return SigType.ECDSA_P256_SHA256;
- } else if (userPublicKey instanceof RSAPublicKey) {
- return SigType.RSA2048_SHA256;
- } else {
- throw new InvalidKeyException("Unsupported key type");
- }
- }
-}
diff --git a/src/main/java/com/google/security/cryptauth/lib/securegcm/Ukey2Handshake.java b/src/main/java/com/google/security/cryptauth/lib/securegcm/Ukey2Handshake.java
deleted file mode 100644
index 8e00ea9..0000000
--- a/src/main/java/com/google/security/cryptauth/lib/securegcm/Ukey2Handshake.java
+++ /dev/null
@@ -1,1041 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.security.cryptauth.lib.securegcm;
-
-import com.google.protobuf.ByteString;
-import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.security.cryptauth.lib.securegcm.UkeyProto.Ukey2Alert;
-import com.google.security.cryptauth.lib.securegcm.UkeyProto.Ukey2ClientFinished;
-import com.google.security.cryptauth.lib.securegcm.UkeyProto.Ukey2ClientInit;
-import com.google.security.cryptauth.lib.securegcm.UkeyProto.Ukey2ClientInit.CipherCommitment;
-import com.google.security.cryptauth.lib.securegcm.UkeyProto.Ukey2Message;
-import com.google.security.cryptauth.lib.securegcm.UkeyProto.Ukey2ServerInit;
-import com.google.security.cryptauth.lib.securemessage.CryptoOps;
-import com.google.security.cryptauth.lib.securemessage.PublicKeyProtoUtil;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.GenericPublicKey;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.security.InvalidKeyException;
-import java.security.KeyPair;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.PublicKey;
-import java.security.SecureRandom;
-import java.security.spec.InvalidKeySpecException;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import javax.annotation.Nullable;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.SecretKeySpec;
-
-/**
- * Implements UKEY2 and produces a {@link D2DConnectionContext}.
- *
- * <p>Client Usage:
- * <code>
- * try {
- * Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
- * byte[] handshakeMessage;
- *
- * // Message 1 (Client Init)
- * handshakeMessage = client.getNextHandshakeMessage();
- * sendMessageToServer(handshakeMessage);
- *
- * // Message 2 (Server Init)
- * handshakeMessage = receiveMessageFromServer();
- * client.parseHandshakeMessage(handshakeMessage);
- *
- * // Message 3 (Client Finish)
- * handshakeMessage = client.getNextHandshakeMessage();
- * sendMessageToServer(handshakeMessage);
- *
- * // Get the auth string
- * byte[] clientAuthString = client.getVerificationString(STRING_LENGTH);
- * showStringToUser(clientAuthString);
- *
- * // Using out-of-band channel, verify auth string, then call:
- * client.verifyHandshake();
- *
- * // Make a connection context
- * D2DConnectionContext clientContext = client.toConnectionContext();
- * } catch (AlertException e) {
- * log(e.getMessage);
- * sendMessageToServer(e.getAlertMessageToSend());
- * } catch (HandshakeException e) {
- * log(e);
- * // terminate handshake
- * }
- * </code>
- *
- * <p>Server Usage:
- * <code>
- * try {
- * Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
- * byte[] handshakeMessage;
- *
- * // Message 1 (Client Init)
- * handshakeMessage = receiveMessageFromClient();
- * server.parseHandshakeMessage(handshakeMessage);
- *
- * // Message 2 (Server Init)
- * handshakeMessage = server.getNextHandshakeMessage();
- * sendMessageToServer(handshakeMessage);
- *
- * // Message 3 (Client Finish)
- * handshakeMessage = receiveMessageFromClient();
- * server.parseHandshakeMessage(handshakeMessage);
- *
- * // Get the auth string
- * byte[] serverAuthString = server.getVerificationString(STRING_LENGTH);
- * showStringToUser(serverAuthString);
- *
- * // Using out-of-band channel, verify auth string, then call:
- * server.verifyHandshake();
- *
- * // Make a connection context
- * D2DConnectionContext serverContext = server.toConnectionContext();
- * } catch (AlertException e) {
- * log(e.getMessage);
- * sendMessageToClient(e.getAlertMessageToSend());
- * } catch (HandshakeException e) {
- * log(e);
- * // terminate handshake
- * }
- * </code>
- */
-public class Ukey2Handshake {
-
- /**
- * Creates a {@link Ukey2Handshake} with a particular cipher that can be used by an initiator /
- * client.
- *
- * @throws HandshakeException
- */
- public static Ukey2Handshake forInitiator(HandshakeCipher cipher) throws HandshakeException {
- return new Ukey2Handshake(InternalState.CLIENT_START, cipher);
- }
-
- /**
- * Creates a {@link Ukey2Handshake} with a particular cipher that can be used by an responder /
- * server.
- *
- * @throws HandshakeException
- */
- public static Ukey2Handshake forResponder(HandshakeCipher cipher) throws HandshakeException {
- return new Ukey2Handshake(InternalState.SERVER_START, cipher);
- }
-
- /**
- * Handshake States. Meaning of states:
- * <ul>
- * <li>IN_PROGRESS: The handshake is in progress, caller should use
- * {@link Ukey2Handshake#getNextHandshakeMessage()} and
- * {@link Ukey2Handshake#parseHandshakeMessage(byte[])} to continue the handshake.
- * <li>VERIFICATION_NEEDED: The handshake is complete, but pending verification of the
- * authentication string. Clients should use {@link Ukey2Handshake#getVerificationString(int)} to
- * get the verification string and use out-of-band methods to authenticate the handshake.
- * <li>VERIFICATION_IN_PROGRESS: The handshake is complete, verification string has been
- * generated, but has not been confirmed. After authenticating the handshake out-of-band, use
- * {@link Ukey2Handshake#verifyHandshake()} to mark the handshake as verified.
- * <li>FINISHED: The handshake is finished, and caller can use
- * {@link Ukey2Handshake#toConnectionContext()} to produce a {@link D2DConnectionContext}.
- * <li>ALREADY_USED: The handshake has already been used and should be discarded / garbage
- * collected.
- * <li>ERROR: The handshake produced an error and should be destroyed.
- * </ul>
- */
- public enum State {
- IN_PROGRESS,
- VERIFICATION_NEEDED,
- VERIFICATION_IN_PROGRESS,
- FINISHED,
- ALREADY_USED,
- ERROR,
- }
-
- /**
- * Currently implemented UKEY2 handshake ciphers. Each cipher is a tuple consisting of a key
- * negotiation cipher and a hash function used for a commitment. Currently the ciphers are:
- * <code>
- * +-----------------------------------------------------+
- * | Enum | Key negotiation | Hash function |
- * +-------------+-----------------------+---------------+
- * | P256_SHA512 | ECDH using NIST P-256 | SHA512 |
- * +-----------------------------------------------------+
- * </code>
- *
- * <p>Note that these should correspond to values in device_to_device_messages.proto.
- */
- public enum HandshakeCipher {
- P256_SHA512(UkeyProto.Ukey2HandshakeCipher.P256_SHA512);
- // TODO(aczeskis): add CURVE25519_SHA512
-
- private final UkeyProto.Ukey2HandshakeCipher value;
-
- HandshakeCipher(UkeyProto.Ukey2HandshakeCipher value) {
- // Make sure we only accept values that are valid as per the ukey protobuf.
- // NOTE: Don't use switch statement on value, as that will trigger a bug. b/30682989.
- if (value == UkeyProto.Ukey2HandshakeCipher.P256_SHA512) {
- this.value = value;
- } else {
- throw new IllegalArgumentException("Unknown cipher value: " + value);
- }
- }
-
- public UkeyProto.Ukey2HandshakeCipher getValue() {
- return value;
- }
- }
-
- /**
- * If thrown, this exception contains information that should be sent on the wire. Specifically,
- * the {@link #getAlertMessageToSend()} method returns a <code>byte[]</code> that communicates the
- * error to the other party in the handshake. Meanwhile, the {@link #getMessage()} method can be
- * used to get a log-able error message.
- */
- public static class AlertException extends Exception {
- private final Ukey2Alert alertMessageToSend;
-
- public AlertException(String alertMessageToLog, Ukey2Alert alertMessageToSend) {
- super(alertMessageToLog);
- this.alertMessageToSend = alertMessageToSend;
- }
-
- /**
- * @return a message suitable for sending to other member of handshake.
- */
- public byte[] getAlertMessageToSend() {
- return alertMessageToSend.toByteArray();
- }
- }
-
- // Maximum version of the handshake supported by this class.
- public static final int VERSION = 1;
-
- // Random nonce is fixed at 32 bytes (as per go/ukey2).
- private static final int NONCE_LENGTH_IN_BYTES = 32;
-
- private static final String UTF_8 = "UTF-8";
-
- // Currently, we only support one next protocol.
- private static final String NEXT_PROTOCOL = "AES_256_CBC-HMAC_SHA256";
-
- // Clients need to store a map of message 3's (client finishes) for each commitment.
- private final HashMap<HandshakeCipher, byte[]> rawMessage3Map = new HashMap<>();
-
- private final HandshakeCipher handshakeCipher;
- private final HandshakeRole handshakeRole;
- private InternalState handshakeState;
- private final KeyPair ourKeyPair;
- private PublicKey theirPublicKey;
- private SecretKey derivedSecretKey;
-
- // Servers need to store client commitments.
- private byte[] theirCommitment;
-
- // We store the raw messages sent for computing the authentication strings and next key.
- private byte[] rawMessage1;
- private byte[] rawMessage2;
-
- // Enums for internal state machinery
- private enum InternalState {
- // Initiator/client state
- CLIENT_START,
- CLIENT_WAITING_FOR_SERVER_INIT,
- CLIENT_AFTER_SERVER_INIT,
-
- // Responder/server state
- SERVER_START,
- SERVER_AFTER_CLIENT_INIT,
- SERVER_WAITING_FOR_CLIENT_FINISHED,
-
- // Common completion state
- HANDSHAKE_VERIFICATION_NEEDED,
- HANDSHAKE_VERIFICATION_IN_PROGRESS,
- HANDSHAKE_FINISHED,
- HANDSHAKE_ALREADY_USED,
- HANDSHAKE_ERROR,
- }
-
- // Helps us remember our role in the handshake
- private enum HandshakeRole {
- CLIENT,
- SERVER
- }
-
- /**
- * Never invoked directly. Caller should use {@link #forInitiator(HandshakeCipher)} or
- * {@link #forResponder(HandshakeCipher)} instead.
- *
- * @throws HandshakeException if an unrecoverable error occurs and the connection should be shut
- * down.
- */
- private Ukey2Handshake(InternalState state, HandshakeCipher cipher) throws HandshakeException {
- if (cipher == null) {
- throwIllegalArgumentException("Invalid handshake cipher");
- }
- this.handshakeCipher = cipher;
-
- switch (state) {
- case CLIENT_START:
- handshakeRole = HandshakeRole.CLIENT;
- break;
- case SERVER_START:
- handshakeRole = HandshakeRole.SERVER;
- break;
- default:
- throwIllegalStateException("Invalid handshake state");
- handshakeRole = null; // unreachable, but makes compiler happy
- }
- this.handshakeState = state;
-
- this.ourKeyPair = genKeyPair(cipher);
- }
-
- /**
- * Get the next handshake message suitable for sending on the wire.
- *
- * @throws HandshakeException if an unrecoverable error occurs and the connection should be shut
- * down.
- */
- public byte[] getNextHandshakeMessage() throws HandshakeException {
- switch (handshakeState) {
- case CLIENT_START:
- rawMessage1 = makeUkey2Message(Ukey2Message.Type.CLIENT_INIT, makeClientInitMessage());
- handshakeState = InternalState.CLIENT_WAITING_FOR_SERVER_INIT;
- return rawMessage1;
-
- case SERVER_AFTER_CLIENT_INIT:
- rawMessage2 = makeUkey2Message(Ukey2Message.Type.SERVER_INIT, makeServerInitMessage());
- handshakeState = InternalState.SERVER_WAITING_FOR_CLIENT_FINISHED;
- return rawMessage2;
-
- case CLIENT_AFTER_SERVER_INIT:
- // Make sure we have a message 3 for the chosen cipher.
- if (!rawMessage3Map.containsKey(handshakeCipher)) {
- throwIllegalStateException(
- "Client state is CLIENT_AFTER_SERVER_INIT, and cipher is "
- + handshakeCipher
- + ", but no corresponding raw client finished message has been generated");
- }
- handshakeState = InternalState.HANDSHAKE_VERIFICATION_NEEDED;
- return rawMessage3Map.get(handshakeCipher);
-
- default:
- throwIllegalStateException("Cannot get next message in state: " + handshakeState);
- return null; // unreachable, but makes compiler happy
- }
- }
-
- /**
- * Returns an authentication string suitable for authenticating the handshake out-of-band. Note
- * that the authentication string can be short (e.g., a 6 digit visual confirmation code). Note:
- * this should only be called when the state returned byte {@link #getHandshakeState()} is
- * {@link State#VERIFICATION_NEEDED}, which means this can only be called once.
- *
- * @param byteLength length of output in bytes. Min length is 1; max length is 32.
- */
- public byte[] getVerificationString(int byteLength) throws HandshakeException {
- if (byteLength < 1 || byteLength > 32) {
- throwIllegalArgumentException("Minimum length is 1 byte, max is 32 bytes");
- }
-
- if (handshakeState != InternalState.HANDSHAKE_VERIFICATION_NEEDED) {
- throwIllegalStateException("Unexpected state: " + handshakeState);
- }
-
- try {
- derivedSecretKey =
- EnrollmentCryptoOps.doKeyAgreement(ourKeyPair.getPrivate(), theirPublicKey);
- } catch (InvalidKeyException e) {
- // unreachable in practice
- throwHandshakeException(e);
- }
-
- ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
- try {
- byteStream.write(rawMessage1);
- byteStream.write(rawMessage2);
- } catch (IOException e) {
- // unreachable in practice
- throwHandshakeException(e);
- }
- byte[] info = byteStream.toByteArray();
-
- byte[] salt = null;
-
- try {
- salt = "UKEY2 v1 auth".getBytes(UTF_8);
- } catch (UnsupportedEncodingException e) {
- // unreachable in practice
- throwHandshakeException(e);
- }
-
- byte[] authString = null;
- try {
- authString = CryptoOps.hkdf(derivedSecretKey, salt, info);
- } catch (InvalidKeyException | NoSuchAlgorithmException e) {
- // unreachable in practice
- throwHandshakeException(e);
- }
-
- handshakeState = InternalState.HANDSHAKE_VERIFICATION_IN_PROGRESS;
- return Arrays.copyOf(authString, byteLength);
- }
-
- /**
- * Invoked to let handshake state machine know that caller has validated the authentication
- * string obtained via {@link #getVerificationString(int)}; Note: this should only be called when
- * the state returned byte {@link #getHandshakeState()} is {@link State#VERIFICATION_IN_PROGRESS}.
- */
- public void verifyHandshake() {
- if (handshakeState != InternalState.HANDSHAKE_VERIFICATION_IN_PROGRESS) {
- throwIllegalStateException("Unexpected state: " + handshakeState);
- }
- handshakeState = InternalState.HANDSHAKE_FINISHED;
- }
-
- /**
- * Parses the given handshake message.
- * @throws AlertException if an error occurs that should be sent to other party.
- * @throws HandshakeException in an error occurs and the connection should be torn down.
- */
- public void parseHandshakeMessage(byte[] handshakeMessage)
- throws AlertException, HandshakeException {
- switch (handshakeState) {
- case SERVER_START:
- parseMessage1(handshakeMessage);
- handshakeState = InternalState.SERVER_AFTER_CLIENT_INIT;
- break;
-
- case CLIENT_WAITING_FOR_SERVER_INIT:
- parseMessage2(handshakeMessage);
- handshakeState = InternalState.CLIENT_AFTER_SERVER_INIT;
- break;
-
- case SERVER_WAITING_FOR_CLIENT_FINISHED:
- parseMessage3(handshakeMessage);
- handshakeState = InternalState.HANDSHAKE_VERIFICATION_NEEDED;
- break;
-
- default:
- throwIllegalStateException("Cannot parse message in state " + handshakeState);
- }
- }
-
- /**
- * Returns the current state of the handshake. See {@link State}.
- */
- public State getHandshakeState() {
- switch (handshakeState) {
- case CLIENT_START:
- case CLIENT_WAITING_FOR_SERVER_INIT:
- case CLIENT_AFTER_SERVER_INIT:
- case SERVER_START:
- case SERVER_WAITING_FOR_CLIENT_FINISHED:
- case SERVER_AFTER_CLIENT_INIT:
- // fallback intended -- these are all in-progress states
- return State.IN_PROGRESS;
-
- case HANDSHAKE_ERROR:
- return State.ERROR;
-
- case HANDSHAKE_VERIFICATION_NEEDED:
- return State.VERIFICATION_NEEDED;
-
- case HANDSHAKE_VERIFICATION_IN_PROGRESS:
- return State.VERIFICATION_IN_PROGRESS;
-
- case HANDSHAKE_FINISHED:
- return State.FINISHED;
-
- case HANDSHAKE_ALREADY_USED:
- return State.ALREADY_USED;
-
- default:
- // unreachable in practice
- throwIllegalStateException("Unknown state");
- return null; // really unreachable, but makes compiler happy
- }
- }
-
- /**
- * Can be called to generate a {@link D2DConnectionContext}. Note: this should only be called
- * when the state returned byte {@link #getHandshakeState()} is {@link State#FINISHED}.
- *
- * @throws HandshakeException
- */
- public D2DConnectionContext toConnectionContext() throws HandshakeException {
- switch (handshakeState) {
- case HANDSHAKE_ERROR:
- throwIllegalStateException("Cannot make context; handshake had error");
- return null; // makes linter happy
- case HANDSHAKE_ALREADY_USED:
- throwIllegalStateException("Cannot reuse handshake context; is has already been used");
- return null; // makes linter happy
- case HANDSHAKE_VERIFICATION_NEEDED:
- throwIllegalStateException("Handshake not verified, cannot create context");
- return null; // makes linter happy
- case HANDSHAKE_FINISHED:
- // We're done, okay to return a context
- break;
- default:
- // unreachable in practice
- throwIllegalStateException("Handshake is not complete; cannot create connection context");
- }
-
- if (derivedSecretKey == null) {
- throwIllegalStateException("Unexpected state error: derived key is null");
- }
-
- ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
- try {
- byteStream.write(rawMessage1);
- byteStream.write(rawMessage2);
- } catch (IOException e) {
- // unreachable in practice
- throwHandshakeException(e);
- }
- byte[] info = byteStream.toByteArray();
-
- byte[] salt = null;
- try {
- salt = "UKEY2 v1 next".getBytes(UTF_8);
- } catch (UnsupportedEncodingException e) {
- // unreachable
- throwHandshakeException(e);
- }
-
- SecretKey nextProtocolKey = null;
- try {
- nextProtocolKey = new SecretKeySpec(CryptoOps.hkdf(derivedSecretKey, salt, info), "AES");
- } catch (InvalidKeyException | NoSuchAlgorithmException e) {
- // unreachable in practice
- throwHandshakeException(e);
- }
-
- SecretKey clientKey = null;
- SecretKey serverKey = null;
- try {
- clientKey = D2DCryptoOps.deriveNewKeyForPurpose(nextProtocolKey, "client");
- serverKey = D2DCryptoOps.deriveNewKeyForPurpose(nextProtocolKey, "server");
- } catch (InvalidKeyException | NoSuchAlgorithmException e) {
- // unreachable in practice
- throwHandshakeException(e);
- }
-
- handshakeState = InternalState.HANDSHAKE_ALREADY_USED;
-
- return new D2DConnectionContextV1(
- handshakeRole == HandshakeRole.CLIENT ? clientKey : serverKey,
- handshakeRole == HandshakeRole.CLIENT ? serverKey : clientKey,
- 0 /* initial encode sequence number */,
- 0 /* initial decode sequence number */);
- }
-
- /**
- * Generates the byte[] encoding of a {@link Ukey2ClientInit} message.
- *
- * @throws HandshakeException
- */
- private byte[] makeClientInitMessage() throws HandshakeException {
- Ukey2ClientInit.Builder clientInit = Ukey2ClientInit.newBuilder();
- clientInit.setVersion(VERSION);
- clientInit.setRandom(ByteString.copyFrom(generateRandomNonce()));
- clientInit.setNextProtocol(NEXT_PROTOCOL);
-
- // At the moment, we only support one cipher
- clientInit.addCipherCommitments(generateP256SHA512Commitment());
-
- return clientInit.build().toByteArray();
- }
-
- /**
- * Generates the byte[] encoding of a {@link Ukey2ServerInit} message.
- */
- private byte[] makeServerInitMessage() {
- Ukey2ServerInit.Builder serverInit = Ukey2ServerInit.newBuilder();
- serverInit.setVersion(VERSION);
- serverInit.setRandom(ByteString.copyFrom(generateRandomNonce()));
- serverInit.setHandshakeCipher(handshakeCipher.getValue());
- serverInit.setPublicKey(
- PublicKeyProtoUtil.encodePublicKey(ourKeyPair.getPublic()).toByteString());
-
- return serverInit.build().toByteArray();
- }
-
- /**
- * Generates a keypair for the provided handshake cipher. Currently only P256_SHA512 is
- * supported.
- *
- * @throws HandshakeException
- */
- private KeyPair genKeyPair(HandshakeCipher cipher) throws HandshakeException {
- switch (cipher) {
- case P256_SHA512:
- return PublicKeyProtoUtil.generateEcP256KeyPair();
- default:
- // Should never happen
- throwHandshakeException("unknown cipher: " + cipher);
- }
- return null; // unreachable, but makes compiler happy
- }
-
- /**
- * Attempts to parse message 1 (which is a wrapped {@link Ukey2ClientInit}). See go/ukey2 for
- * details.
- *
- * @throws AlertException if an error occurs
- */
- private void parseMessage1(byte[] handshakeMessage) throws AlertException, HandshakeException {
- // Deserialize the protobuf; send a BAD_MESSAGE message if deserialization fails
- Ukey2Message message = null;
- try {
- message = Ukey2Message.parseFrom(handshakeMessage);
- } catch (InvalidProtocolBufferException e) {
- throwAlertException(Ukey2Alert.AlertType.BAD_MESSAGE,
- "Can't parse message 1 " + e.getMessage());
- }
-
- // Verify that message_type == Type.CLIENT_INIT; send a BAD_MESSAGE_TYPE message if mismatch
- if (!message.hasMessageType() || message.getMessageType() != Ukey2Message.Type.CLIENT_INIT) {
- throwAlertException(
- Ukey2Alert.AlertType.BAD_MESSAGE_TYPE,
- "Expected, but did not find ClientInit message type");
- }
-
- // Deserialize message_data as a ClientInit message; send a BAD_MESSAGE_DATA message if
- // deserialization fails
- if (!message.hasMessageData()) {
- throwAlertException(Ukey2Alert.AlertType.BAD_MESSAGE_DATA,
- "Expected message data, but didn't find it");
- }
- Ukey2ClientInit clientInit = null;
- try {
- clientInit = Ukey2ClientInit.parseFrom(message.getMessageData());
- } catch (InvalidProtocolBufferException e) {
- throwAlertException(Ukey2Alert.AlertType.BAD_MESSAGE_DATA,
- "Can't parse message data into ClientInit");
- }
-
- // Check that version == VERSION; send BAD_VERSION message if mismatch
- if (!clientInit.hasVersion()) {
- throwAlertException(Ukey2Alert.AlertType.BAD_VERSION, "ClientInit missing version");
- }
- if (clientInit.getVersion() != VERSION) {
- throwAlertException(Ukey2Alert.AlertType.BAD_VERSION, "ClientInit version mismatch");
- }
-
- // Check that random is exactly NONCE_LENGTH_IN_BYTES bytes; send Alert.BAD_RANDOM message if
- // not.
- if (!clientInit.hasRandom()) {
- throwAlertException(Ukey2Alert.AlertType.BAD_RANDOM, "ClientInit missing random");
- }
- if (clientInit.getRandom().toByteArray().length != NONCE_LENGTH_IN_BYTES) {
- throwAlertException(Ukey2Alert.AlertType.BAD_RANDOM, "ClientInit has incorrect nonce length");
- }
-
- // Check to see if any of the handshake_cipher in cipher_commitment are acceptable. Servers
- // should select the first handshake_cipher that it finds acceptable to support clients
- // signaling deprecated but supported HandshakeCiphers. If no handshake_cipher is acceptable
- // (or there are no HandshakeCiphers in the message), the server sends a BAD_HANDSHAKE_CIPHER
- // message
- List<Ukey2ClientInit.CipherCommitment> commitments = clientInit.getCipherCommitmentsList();
- if (commitments.isEmpty()) {
- throwAlertException(
- Ukey2Alert.AlertType.BAD_HANDSHAKE_CIPHER, "ClientInit is missing cipher commitments");
- }
- for (Ukey2ClientInit.CipherCommitment commitment : commitments) {
- if (!commitment.hasHandshakeCipher()
- || !commitment.hasCommitment()) {
- throwAlertException(
- Ukey2Alert.AlertType.BAD_HANDSHAKE_CIPHER,
- "ClientInit has improperly formatted cipher commitment");
- }
-
- // TODO(aczeskis): for now we only support one cipher, eventually support more
- if (commitment.getHandshakeCipher() == handshakeCipher.getValue()) {
- theirCommitment = commitment.getCommitment().toByteArray();
- }
- }
- if (theirCommitment == null) {
- throwAlertException(Ukey2Alert.AlertType.BAD_HANDSHAKE_CIPHER,
- "No acceptable commitments found");
- }
-
- // Checks that next_protocol contains a protocol that the server supports. Send a
- // BAD_NEXT_PROTOCOL message if not. We currently only support one protocol
- if (!clientInit.hasNextProtocol() || !NEXT_PROTOCOL.equals(clientInit.getNextProtocol())) {
- throwAlertException(Ukey2Alert.AlertType.BAD_NEXT_PROTOCOL, "Incorrect next protocol");
- }
-
- // Store raw message for AUTH_STRING computation
- rawMessage1 = handshakeMessage;
- }
-
- /**
- * Attempts to parse message 2 (which is a wrapped {@link Ukey2ServerInit}). See go/ukey2 for
- * details.
- */
- private void parseMessage2(final byte[] handshakeMessage)
- throws AlertException, HandshakeException {
- // Deserialize the protobuf; send a BAD_MESSAGE message if deserialization fails
- Ukey2Message message = null;
- try {
- message = Ukey2Message.parseFrom(handshakeMessage);
- } catch (InvalidProtocolBufferException e) {
- throwAlertException(Ukey2Alert.AlertType.BAD_MESSAGE,
- "Can't parse message 2 " + e.getMessage());
- }
-
- // Verify that message_type == Type.SERVER_INIT; send a BAD_MESSAGE_TYPE message if mismatch
- if (!message.hasMessageType()) {
- throwAlertException(Ukey2Alert.AlertType.BAD_MESSAGE_TYPE,
- "Expected, but did not find message type");
- }
- if (message.getMessageType() == Ukey2Message.Type.ALERT) {
- handshakeState = InternalState.HANDSHAKE_ERROR;
- throwHandshakeMessageFromAlertMessage(message);
- }
- if (message.getMessageType() != Ukey2Message.Type.SERVER_INIT) {
- throwAlertException(
- Ukey2Alert.AlertType.BAD_MESSAGE_TYPE,
- "Expected, but did not find SERVER_INIT message type");
- }
-
- // Deserialize message_data as a ServerInit message; send a BAD_MESSAGE_DATA message if
- // deserialization fails
- if (!message.hasMessageData()) {
-
- throwAlertException(Ukey2Alert.AlertType.BAD_MESSAGE_DATA,
- "Expected message data, but didn't find it");
- }
- Ukey2ServerInit serverInit = null;
- try {
- serverInit = Ukey2ServerInit.parseFrom(message.getMessageData());
- } catch (InvalidProtocolBufferException e) {
- throwAlertException(Ukey2Alert.AlertType.BAD_MESSAGE_DATA,
- "Can't parse message data into ServerInit");
- }
-
- // Check that version == VERSION; send BAD_VERSION message if mismatch
- if (!serverInit.hasVersion()) {
- throwAlertException(Ukey2Alert.AlertType.BAD_VERSION, "ServerInit missing version");
- }
- if (serverInit.getVersion() != VERSION) {
- throwAlertException(Ukey2Alert.AlertType.BAD_VERSION, "ServerInit version mismatch");
- }
-
- // Check that random is exactly NONCE_LENGTH_IN_BYTES bytes; send Alert.BAD_RANDOM message if
- // not.
- if (!serverInit.hasRandom()) {
- throwAlertException(Ukey2Alert.AlertType.BAD_RANDOM, "ServerInit missing random");
- }
- if (serverInit.getRandom().toByteArray().length != NONCE_LENGTH_IN_BYTES) {
- throwAlertException(Ukey2Alert.AlertType.BAD_RANDOM, "ServerInit has incorrect nonce length");
- }
-
- // Check that handshake_cipher matches a handshake cipher that was sent in
- // ClientInit.cipher_commitments. If not, send a BAD_HANDSHAKECIPHER message
- if (!serverInit.hasHandshakeCipher()) {
- throwAlertException(Ukey2Alert.AlertType.BAD_HANDSHAKE_CIPHER, "No handshake cipher found");
- }
- HandshakeCipher serverCipher = null;
- for (HandshakeCipher cipher : HandshakeCipher.values()) {
- if (cipher.getValue() == serverInit.getHandshakeCipher()) {
- serverCipher = cipher;
- break;
- }
- }
- if (serverCipher == null || serverCipher != handshakeCipher) {
- throwAlertException(Ukey2Alert.AlertType.BAD_HANDSHAKE_CIPHER,
- "No acceptable handshake cipher found");
- }
-
- // Check that public_key parses into a correct public key structure. If not, send a
- // BAD_PUBLIC_KEY message.
- if (!serverInit.hasPublicKey()) {
- throwAlertException(Ukey2Alert.AlertType.BAD_PUBLIC_KEY, "No public key found in ServerInit");
- }
- theirPublicKey = parseP256PublicKey(serverInit.getPublicKey().toByteArray());
-
- // Store raw message for AUTH_STRING computation
- rawMessage2 = handshakeMessage;
- }
-
- /**
- * Attempts to parse message 3 (which is a wrapped {@link Ukey2ClientFinished}). See go/ukey2 for
- * details.
- */
- private void parseMessage3(final byte[] handshakeMessage) throws HandshakeException {
- // Deserialize the protobuf; terminate the connection if deserialization fails.
- Ukey2Message message = null;
- try {
- message = Ukey2Message.parseFrom(handshakeMessage);
- } catch (InvalidProtocolBufferException e) {
- throwHandshakeException("Can't parse message 3", e);
- }
-
- // Verify that message_type == Type.CLIENT_FINISH; terminate connection if mismatch occurs
- if (!message.hasMessageType()) {
- throw new HandshakeException("Expected, but did not find message type");
- }
- if (message.getMessageType() == Ukey2Message.Type.ALERT) {
- throwHandshakeMessageFromAlertMessage(message);
- }
- if (message.getMessageType() != Ukey2Message.Type.CLIENT_FINISH) {
- throwHandshakeException("Expected, but did not find CLIENT_FINISH message type");
- }
-
- // Verify that the hash of the ClientFinished matches the expected commitment from ClientInit.
- // Terminate the connection if the expected match fails.
- verifyCommitment(handshakeMessage);
-
- // Deserialize message_data as a ClientFinished message; terminate the connection if
- // deserialization fails.
- if (!message.hasMessageData()) {
- throwHandshakeException("Expected message data, but didn't find it");
- }
- Ukey2ClientFinished clientFinished = null;
- try {
- clientFinished = Ukey2ClientFinished.parseFrom(message.getMessageData());
- } catch (InvalidProtocolBufferException e) {
- throwHandshakeException(e);
- }
-
- // Check that public_key parses into a correct public key structure. If not, terminate the
- // connection.
- if (!clientFinished.hasPublicKey()) {
- throwHandshakeException("No public key found in ClientFinished");
- }
- try {
- theirPublicKey = parseP256PublicKey(clientFinished.getPublicKey().toByteArray());
- } catch (AlertException e) {
- // Wrap in a HandshakeException because error should not be sent on the wire.
- throwHandshakeException(e);
- }
- }
-
- private void verifyCommitment(byte[] handshakeMessage) throws HandshakeException {
- byte[] actualClientFinishHash = null;
- switch (handshakeCipher) {
- case P256_SHA512:
- actualClientFinishHash = sha512(handshakeMessage);
- break;
- default:
- // should be unreachable
- throwIllegalStateException("Unexpected handshakeCipher");
- }
-
- // Time constant after Java SE 6 Update 17
- // See http://www.oracle.com/technetwork/java/javase/6u17-141447.html
- if (!MessageDigest.isEqual(actualClientFinishHash, theirCommitment)) {
- throwHandshakeException("Commitment does not match");
- }
- }
-
- private void throwHandshakeMessageFromAlertMessage(Ukey2Message message)
- throws HandshakeException {
- if (message.hasMessageData()) {
- Ukey2Alert alert = null;
- try {
- alert = Ukey2Alert.parseFrom(message.getMessageData());
- } catch (InvalidProtocolBufferException e) {
- throwHandshakeException("Cannot parse alert message", e);
- }
-
- if (alert.hasType() && alert.hasErrorMessage()) {
- throwHandshakeException(
- "Received Alert message. Type: "
- + alert.getType()
- + " Error Message: "
- + alert.getErrorMessage());
- } else if (alert.hasType()) {
- throwHandshakeException("Received Alert message. Type: " + alert.getType());
- }
- }
-
- throwHandshakeException("Received empty Alert Message");
- }
-
- /**
- * Parses an encoded public P256 key.
- */
- private PublicKey parseP256PublicKey(byte[] encodedPublicKey)
- throws AlertException, HandshakeException {
- try {
- return PublicKeyProtoUtil.parsePublicKey(GenericPublicKey.parseFrom(encodedPublicKey));
- } catch (InvalidProtocolBufferException | InvalidKeySpecException e) {
- throwAlertException(Ukey2Alert.AlertType.BAD_PUBLIC_KEY,
- "Cannot parse public key: " + e.getMessage());
- return null; // unreachable, but makes compiler happy
- }
- }
-
- /**
- * Generates a {@link CipherCommitment} for the P256_SHA512 cipher.
- */
- private CipherCommitment generateP256SHA512Commitment() throws HandshakeException {
- // Generate the corresponding finished message if it's not done yet
- if (!rawMessage3Map.containsKey(HandshakeCipher.P256_SHA512)) {
- generateP256SHA512ClientFinished(ourKeyPair);
- }
-
- CipherCommitment.Builder cipherCommitment = CipherCommitment.newBuilder();
- cipherCommitment.setHandshakeCipher(UkeyProto.Ukey2HandshakeCipher.P256_SHA512);
- cipherCommitment.setCommitment(
- ByteString.copyFrom(sha512(rawMessage3Map.get(HandshakeCipher.P256_SHA512))));
-
- return cipherCommitment.build();
- }
-
- /**
- * Generates and records a {@link Ukey2ClientFinished} message for the P256_SHA512 cipher.
- */
- private Ukey2ClientFinished generateP256SHA512ClientFinished(KeyPair p256KeyPair) {
- byte[] encodedKey = PublicKeyProtoUtil.encodePublicKey(p256KeyPair.getPublic()).toByteArray();
-
- Ukey2ClientFinished.Builder clientFinished = Ukey2ClientFinished.newBuilder();
- clientFinished.setPublicKey(ByteString.copyFrom(encodedKey));
-
- rawMessage3Map.put(
- HandshakeCipher.P256_SHA512,
- makeUkey2Message(Ukey2Message.Type.CLIENT_FINISH, clientFinished.build().toByteArray()));
-
- return clientFinished.build();
- }
-
- /**
- * Generates the serialized representation of a {@link Ukey2Message} based on the provided type
- * and data.
- */
- private byte[] makeUkey2Message(Ukey2Message.Type messageType, byte[] messageData) {
- Ukey2Message.Builder message = Ukey2Message.newBuilder();
-
- switch (messageType) {
- case ALERT:
- case CLIENT_INIT:
- case SERVER_INIT:
- case CLIENT_FINISH:
- // fall through intentional; valid message types
- break;
- default:
- throwIllegalArgumentException("Invalid message type: " + messageType);
- }
- message.setMessageType(messageType);
-
- // Alerts a blank message data field
- if (messageType != Ukey2Message.Type.ALERT) {
- if (messageData == null || messageData.length == 0) {
- throwIllegalArgumentException("Cannot send empty message data for non-alert messages");
- }
- message.setMessageData(ByteString.copyFrom(messageData));
- }
-
- return message.build().toByteArray();
- }
-
- /**
- * Returns a {@link Ukey2Alert} message of given type and having the loggable additional data if
- * present.
- */
- private Ukey2Alert makeAlertMessage(Ukey2Alert.AlertType alertType,
- @Nullable String loggableAdditionalData) throws HandshakeException {
- switch (alertType) {
- case BAD_MESSAGE:
- case BAD_MESSAGE_TYPE:
- case INCORRECT_MESSAGE:
- case BAD_MESSAGE_DATA:
- case BAD_VERSION:
- case BAD_RANDOM:
- case BAD_HANDSHAKE_CIPHER:
- case BAD_NEXT_PROTOCOL:
- case BAD_PUBLIC_KEY:
- case INTERNAL_ERROR:
- // fall through intentional; valid alert types
- break;
- default:
- throwHandshakeException("Unknown alert type: " + alertType);
- }
-
- Ukey2Alert.Builder alert = Ukey2Alert.newBuilder();
- alert.setType(alertType);
-
- if (loggableAdditionalData != null) {
- alert.setErrorMessage(loggableAdditionalData);
- }
-
- return alert.build();
- }
-
- /**
- * Generates a cryptoraphically random nonce of NONCE_LENGTH_IN_BYTES bytes.
- */
- private static byte[] generateRandomNonce() {
- SecureRandom rng = new SecureRandom();
- byte[] randomNonce = new byte[NONCE_LENGTH_IN_BYTES];
- rng.nextBytes(randomNonce);
- return randomNonce;
- }
-
- /**
- * Handy wrapper to do SHA512.
- */
- private byte[] sha512(byte[] input) throws HandshakeException {
- MessageDigest sha512;
- try {
- sha512 = MessageDigest.getInstance("SHA-512");
- return sha512.digest(input);
- } catch (NoSuchAlgorithmException e) {
- throwHandshakeException("No security provider initialized yet?", e);
- return null; // unreachable in practice, but makes compiler happy
- }
- }
-
- // Exception wrappers that remember to set the handshake state to ERROR
-
- private void throwAlertException(Ukey2Alert.AlertType alertType, String alertLogStatement)
- throws AlertException, HandshakeException {
- handshakeState = InternalState.HANDSHAKE_ERROR;
- throw new AlertException(alertLogStatement, makeAlertMessage(alertType, alertLogStatement));
- }
-
- private void throwHandshakeException(String logMessage) throws HandshakeException {
- handshakeState = InternalState.HANDSHAKE_ERROR;
- throw new HandshakeException(logMessage);
- }
-
- private void throwHandshakeException(Exception e) throws HandshakeException {
- handshakeState = InternalState.HANDSHAKE_ERROR;
- throw new HandshakeException(e);
- }
-
- private void throwHandshakeException(String logMessage, Exception e) throws HandshakeException {
- handshakeState = InternalState.HANDSHAKE_ERROR;
- throw new HandshakeException(logMessage, e);
- }
-
- private void throwIllegalStateException(String logMessage) {
- handshakeState = InternalState.HANDSHAKE_ERROR;
- throw new IllegalStateException(logMessage);
- }
-
- private void throwIllegalArgumentException(String logMessage) {
- handshakeState = InternalState.HANDSHAKE_ERROR;
- throw new IllegalArgumentException(logMessage);
- }
-}
diff --git a/src/main/java/com/google/security/cryptauth/lib/securegcm/device_to_device_messages_config.asciipb b/src/main/java/com/google/security/cryptauth/lib/securegcm/device_to_device_messages_config.asciipb
deleted file mode 100644
index 0e2952c..0000000
--- a/src/main/java/com/google/security/cryptauth/lib/securegcm/device_to_device_messages_config.asciipb
+++ /dev/null
@@ -1,3 +0,0 @@
-optimize_mode: LITE_RUNTIME
-
-allowed_message: "securegcm.DeviceToDeviceMessage"
diff --git a/src/main/java/com/google/security/cryptauth/lib/securegcm/securegcm_config.asciipb b/src/main/java/com/google/security/cryptauth/lib/securegcm/securegcm_config.asciipb
deleted file mode 100644
index d838bd3..0000000
--- a/src/main/java/com/google/security/cryptauth/lib/securegcm/securegcm_config.asciipb
+++ /dev/null
@@ -1,4 +0,0 @@
-optimize_mode: LITE_RUNTIME
-
-allowed_enum: "securegcm.Type"
-allowed_message: "securegcm.GcmMetadata"
diff --git a/src/main/java/com/google/security/cryptauth/lib/securemessage/CryptoOps.java b/src/main/java/com/google/security/cryptauth/lib/securemessage/CryptoOps.java
deleted file mode 100644
index 876bd93..0000000
--- a/src/main/java/com/google/security/cryptauth/lib/securemessage/CryptoOps.java
+++ /dev/null
@@ -1,564 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.security.cryptauth.lib.securemessage;
-
-import com.google.security.annotations.SuppressInsecureCipherModeCheckerReviewed;
-import java.io.UnsupportedEncodingException;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.Key;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.SecureRandom;
-import java.security.Signature;
-import java.security.SignatureException;
-import javax.annotation.Nullable;
-import javax.crypto.BadPaddingException;
-import javax.crypto.Cipher;
-import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.Mac;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.IvParameterSpec;
-import javax.crypto.spec.SecretKeySpec;
-
-/**
- * Encapsulates the cryptographic operations used by the {@code SecureMessage*} classes.
- */
-public class CryptoOps {
-
- private CryptoOps() {} // Do not instantiate
-
- /**
- * Enum of supported signature types, with additional mappings to indicate the name of the
- * underlying JCA algorithm used to create the signature.
- * @see <a href=
- * "http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html">
- * Java Cryptography Architecture, Standard Algorithm Name Documentation</a>
- */
- public enum SigType {
- HMAC_SHA256(SecureMessageProto.SigScheme.HMAC_SHA256, "HmacSHA256", false),
- ECDSA_P256_SHA256(SecureMessageProto.SigScheme.ECDSA_P256_SHA256, "SHA256withECDSA", true),
- RSA2048_SHA256(SecureMessageProto.SigScheme.RSA2048_SHA256, "SHA256withRSA", true);
-
- public SecureMessageProto.SigScheme getSigScheme() {
- return sigScheme;
- }
-
- public String getJcaName() {
- return jcaName;
- }
-
- public boolean isPublicKeyScheme() {
- return publicKeyScheme;
- }
-
- public static SigType valueOf(SecureMessageProto.SigScheme sigScheme) {
- for (SigType value : values()) {
- if (value.sigScheme.equals(sigScheme)) {
- return value;
- }
- }
- throw new IllegalArgumentException("Unsupported SigType: " + sigScheme);
- }
-
- private final SecureMessageProto.SigScheme sigScheme;
- private final String jcaName;
- private final boolean publicKeyScheme;
-
- SigType(SecureMessageProto.SigScheme sigType, String jcaName, boolean publicKeyScheme) {
- this.sigScheme = sigType;
- this.jcaName = jcaName;
- this.publicKeyScheme = publicKeyScheme;
- }
- }
-
- /**
- * Enum of supported encryption types, with additional mappings to indicate the name of the
- * underlying JCA algorithm used to perform the encryption.
- * @see <a href=
- * "http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html">
- * Java Cryptography Architecture, Standard Algorithm Name Documentation</a>
- */
- public enum EncType {
- NONE(SecureMessageProto.EncScheme.NONE, "InvalidDoNotUseForJCA"),
- AES_256_CBC(SecureMessageProto.EncScheme.AES_256_CBC, "AES/CBC/PKCS5Padding");
-
- public SecureMessageProto.EncScheme getEncScheme() {
- return encScheme;
- }
-
- public String getJcaName() {
- return jcaName;
- }
-
- public static EncType valueOf(SecureMessageProto.EncScheme encScheme) {
- for (EncType value : values()) {
- if (value.encScheme.equals(encScheme)) {
- return value;
- }
- }
- throw new IllegalArgumentException("Unsupported EncType: " + encScheme);
- }
-
- private final SecureMessageProto.EncScheme encScheme;
- private final String jcaName;
-
- EncType(SecureMessageProto.EncScheme encScheme, String jcaName) {
- this.encScheme = encScheme;
- this.jcaName = jcaName;
- }
- }
-
- /**
- * Truncated hash output length, in bytes.
- */
- static final int DIGEST_LENGTH = 20;
- /**
- * A salt value specific to this library, generated as SHA-256("SecureMessage")
- */
- private static final byte[] SALT = sha256("SecureMessage");
-
- /**
- * Signs {@code data} using the algorithm specified by {@code sigType} with {@code signingKey}.
- *
- * @param rng is required for public key signature schemes
- * @return raw signature
- * @throws InvalidKeyException if {@code signingKey} is incompatible with {@code sigType}
- * @throws NoSuchAlgorithmException if the security provider is inadequate for {@code sigType}
- */
- static byte[] sign(
- SigType sigType, Key signingKey, @Nullable SecureRandom rng, byte[] data)
- throws InvalidKeyException, NoSuchAlgorithmException {
- if ((signingKey == null) || (data == null)) {
- throw new NullPointerException();
- }
- if (sigType.isPublicKeyScheme()) {
- if (rng == null) {
- throw new NullPointerException();
- }
- if (!(signingKey instanceof PrivateKey)) {
- throw new InvalidKeyException("Expected a PrivateKey");
- }
- Signature sigScheme = Signature.getInstance(sigType.getJcaName());
- sigScheme.initSign((PrivateKey) signingKey, rng);
- try {
- // We include a fixed magic value (salt) in the signature so that if the signing key is
- // reused in another context we can't be confused -- provided that the other user of the
- // signing key only signs statements that do not begin with this salt.
- sigScheme.update(SALT);
- sigScheme.update(data);
- return sigScheme.sign();
- } catch (SignatureException e) {
- throw new IllegalStateException(e); // Consistent with failures in Mac.doFinal
- }
- } else {
- Mac macScheme = Mac.getInstance(sigType.getJcaName());
- // Note that an AES-256 SecretKey should work with most Mac schemes
- SecretKey derivedKey = deriveAes256KeyFor(getSecretKey(signingKey), getPurpose(sigType));
- macScheme.init(derivedKey);
- return macScheme.doFinal(data);
- }
- }
-
- /**
- * Verifies the {@code signature} on {@code data} using the algorithm specified by
- * {@code sigType} with {@code verificationKey}.
- *
- * @return true iff the signature is verified
- * @throws NoSuchAlgorithmException if the security provider is inadequate for {@code sigType}
- * @throws InvalidKeyException if {@code verificationKey} is incompatible with {@code sigType}
- * @throws SignatureException
- */
- static boolean verify(Key verificationKey, SigType sigType, byte[] signature, byte[] data)
- throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
- if ((verificationKey == null) || (signature == null) || (data == null)) {
- throw new NullPointerException();
- }
- if (sigType.isPublicKeyScheme()) {
- if (!(verificationKey instanceof PublicKey)) {
- throw new InvalidKeyException("Expected a PublicKey");
- }
- Signature sigScheme = Signature.getInstance(sigType.getJcaName());
- sigScheme.initVerify((PublicKey) verificationKey);
- sigScheme.update(SALT); // See the comments in sign() for more on this
- sigScheme.update(data);
- return sigScheme.verify(signature);
- } else {
- Mac macScheme = Mac.getInstance(sigType.getJcaName());
- SecretKey derivedKey =
- deriveAes256KeyFor(getSecretKey(verificationKey), getPurpose(sigType));
- macScheme.init(derivedKey);
- return constantTimeArrayEquals(signature, macScheme.doFinal(data));
- }
- }
-
- /**
- * Generate a random IV appropriate for use with the algorithm specified in {@code encType}.
- *
- * @return a freshly generated IV (a random byte sequence of appropriate length)
- * @throws NoSuchAlgorithmException if the security provider is inadequate for {@code encType}
- */
- @SuppressInsecureCipherModeCheckerReviewed
- // See b/26525455 for security review.
- static byte[] generateIv(EncType encType, SecureRandom rng) throws NoSuchAlgorithmException {
- if (rng == null) {
- throw new NullPointerException();
- }
- try {
- Cipher encrypter = Cipher.getInstance(encType.getJcaName());
- byte[] iv = new byte[encrypter.getBlockSize()];
- rng.nextBytes(iv);
- return iv;
- } catch (NoSuchPaddingException e) {
- throw new NoSuchAlgorithmException(e); // Consolidate into NoSuchAlgorithmException
- }
- }
-
- /**
- * Encrypts {@code plaintext} using the algorithm specified in {@code encType}, with the specified
- * {@code iv} and {@code encryptionKey}.
- *
- * @param rng source of randomness to be used with the specified cipher, if necessary
- * @return encrypted data
- * @throws NoSuchAlgorithmException if the security provider is inadequate for {@code encType}
- * @throws InvalidKeyException if {@code encryptionKey} is incompatible with {@code encType}
- */
- @SuppressInsecureCipherModeCheckerReviewed
- // See b/26525455 for security review.
- static byte[] encrypt(
- Key encryptionKey, EncType encType, @Nullable SecureRandom rng, byte[] iv, byte[] plaintext)
- throws NoSuchAlgorithmException, InvalidKeyException {
- if ((encryptionKey == null) || (iv == null) || (plaintext == null)) {
- throw new NullPointerException();
- }
- if (encType == EncType.NONE) {
- throw new NoSuchAlgorithmException("Cannot use NONE type here");
- }
- try {
- Cipher encrypter = Cipher.getInstance(encType.getJcaName());
- SecretKey derivedKey =
- deriveAes256KeyFor(getSecretKey(encryptionKey), getPurpose(encType));
- encrypter.init(Cipher.ENCRYPT_MODE, derivedKey, new IvParameterSpec(iv), rng);
- return encrypter.doFinal(plaintext);
- } catch (InvalidAlgorithmParameterException e) {
- throw new AssertionError(e); // Should never happen
- } catch (IllegalBlockSizeException e) {
- throw new AssertionError(e); // Should never happen
- } catch (BadPaddingException e) {
- throw new AssertionError(e); // Should never happen
- } catch (NoSuchPaddingException e) {
- throw new NoSuchAlgorithmException(e); // Consolidate into NoSuchAlgorithmException
- }
- }
-
- /**
- * Decrypts {@code ciphertext} using the algorithm specified in {@code encType}, with the
- * specified {@code iv} and {@code decryptionKey}.
- *
- * @return the plaintext (decrypted) data
- * @throws NoSuchAlgorithmException if the security provider is inadequate for {@code encType}
- * @throws InvalidKeyException if {@code decryptionKey} is incompatible with {@code encType}
- * @throws InvalidAlgorithmParameterException if {@code encType} exceeds legal cryptographic
- * strength limits in this jurisdiction
- * @throws IllegalBlockSizeException if {@code ciphertext} contains an illegal block
- * @throws BadPaddingException if {@code ciphertext} contains an illegal padding
- */
- @SuppressInsecureCipherModeCheckerReviewed
- // See b/26525455 for security review
- static byte[] decrypt(Key decryptionKey, EncType encType, byte[] iv, byte[] ciphertext)
- throws NoSuchAlgorithmException, InvalidKeyException, InvalidAlgorithmParameterException,
- IllegalBlockSizeException, BadPaddingException {
- if ((decryptionKey == null) || (iv == null) || (ciphertext == null)) {
- throw new NullPointerException();
- }
- if (encType == EncType.NONE) {
- throw new NoSuchAlgorithmException("Cannot use NONE type here");
- }
- try {
- Cipher decrypter = Cipher.getInstance(encType.getJcaName());
- SecretKey derivedKey =
- deriveAes256KeyFor(getSecretKey(decryptionKey), getPurpose(encType));
- decrypter.init(Cipher.DECRYPT_MODE, derivedKey, new IvParameterSpec(iv));
- return decrypter.doFinal(ciphertext);
- } catch (NoSuchPaddingException e) {
- throw new AssertionError(e); // Should never happen
- }
- }
-
- /**
- * Computes a collision-resistant hash of {@link #DIGEST_LENGTH} bytes
- * (using a truncated SHA-256 output).
- */
- static byte[] digest(byte[] data) throws NoSuchAlgorithmException {
- MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
- byte[] truncatedHash = new byte[DIGEST_LENGTH];
- System.arraycopy(sha256.digest(data), 0, truncatedHash, 0, DIGEST_LENGTH);
- return truncatedHash;
- }
-
- /**
- * Returns {@code true} if the two arrays are equal to one another.
- * When the two arrays differ in length, trivially returns {@code false}.
- * When the two arrays are equal in length, does a constant-time comparison
- * of the two, i.e. does not abort the comparison when the first differing
- * element is found.
- *
- * <p>NOTE: This is a copy of {@code java/com/google/math/crypto/ConstantTime#arrayEquals}.
- *
- * @param a An array to compare
- * @param b Another array to compare
- * @return {@code true} if these arrays are both null or if they have equal
- * length and equal bytes in all elements
- */
- static boolean constantTimeArrayEquals(@Nullable byte[] a, @Nullable byte[] b) {
- if (a == null || b == null) {
- return (a == b);
- }
- if (a.length != b.length) {
- return false;
- }
- byte result = 0;
- for (int i = 0; i < b.length; i++) {
- result = (byte) (result | a[i] ^ b[i]);
- }
- return (result == 0);
- }
-
- // @VisibleForTesting
- static String getPurpose(SigType sigType) {
- return "SIG:" + sigType.getSigScheme().getNumber();
- }
-
- // @VisibleForTesting
- static String getPurpose(EncType encType) {
- return "ENC:" + encType.getEncScheme().getNumber();
- }
-
- private static SecretKey getSecretKey(Key key) throws InvalidKeyException {
- if (!(key instanceof SecretKey)) {
- throw new InvalidKeyException("Expected a SecretKey");
- }
- return (SecretKey) key;
- }
-
- /**
- * @return the UTF-8 encoding of the given string
- * @throws RuntimeException if the UTF-8 charset is not present.
- */
- public static byte[] utf8StringToBytes(String input) {
- try {
- return input.getBytes("UTF-8");
- } catch (UnsupportedEncodingException e) {
- throw new RuntimeException(e); // Shouldn't happen, UTF-8 is universal
- }
- }
-
- /**
- * @return SHA-256(UTF-8 encoded input)
- */
- public static byte[] sha256(String input) {
- MessageDigest sha256;
- try {
- sha256 = MessageDigest.getInstance("SHA-256");
- return sha256.digest(utf8StringToBytes(input));
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException("No security provider initialized yet?", e);
- }
- }
-
- /**
- * A key derivation function specific to this library, which accepts a {@code masterKey} and an
- * arbitrary {@code purpose} describing the intended application of the derived sub-key,
- * and produces a derived AES-256 key safe to use as if it were independent of any other
- * derived key which used a different {@code purpose}.
- *
- * @param masterKey any key suitable for use with HmacSHA256
- * @param purpose a UTF-8 encoded string describing the intended purpose of derived key
- * @return a derived SecretKey suitable for use with AES-256
- * @throws InvalidKeyException if the encoded form of {@code masterKey} cannot be accessed
- */
- static SecretKey deriveAes256KeyFor(SecretKey masterKey, String purpose)
- throws NoSuchAlgorithmException, InvalidKeyException {
- return new SecretKeySpec(hkdf(masterKey, SALT, utf8StringToBytes(purpose)), "AES");
- }
-
- /**
- * Implements HKDF (RFC 5869) with the SHA-256 hash and a 256-bit output key length.
- *
- * Please make sure to select a salt that is fixed and unique for your codebase, and use the
- * {@code info} parameter to specify any additional bits that should influence the derived key.
- *
- * @param inputKeyMaterial master key from which to derive sub-keys
- * @param salt a (public) randomly generated 256-bit input that can be re-used
- * @param info arbitrary information that is bound to the derived key (i.e., used in its creation)
- * @return raw derived key bytes = HKDF-SHA256(inputKeyMaterial, salt, info)
- * @throws InvalidKeyException if the encoded form of {@code inputKeyMaterial} cannot be accessed
- */
- public static byte[] hkdf(SecretKey inputKeyMaterial, byte[] salt, byte[] info)
- throws NoSuchAlgorithmException, InvalidKeyException {
- return hkdf(inputKeyMaterial, salt, info, /* length= */ 32);
- }
-
- /**
- * Implements HKDF (RFC 5869) with the SHA-256 hash.
- *
- * <p>Please make sure to select a salt that is fixed and unique for your codebase, and use the
- * {@code info} parameter to specify any additional bits that should influence the derived key.
- *
- * @param inputKeyMaterial master key from which to derive sub-keys
- * @param salt a (public) randomly generated 256-bit input that can be re-used
- * @param info arbitrary information that is bound to the derived key (i.e., used in its creation)
- * @param length length of returned key material
- * @return raw derived key bytes = HKDF-SHA256(inputKeyMaterial, salt, info)
- * @throws InvalidKeyException if the encoded form of {@code inputKeyMaterial} cannot be accessed
- */
- public static byte[] hkdf(SecretKey inputKeyMaterial, byte[] salt, byte[] info, int length)
- throws NoSuchAlgorithmException, InvalidKeyException {
- if ((inputKeyMaterial == null) || (salt == null) || (info == null)) {
- throw new NullPointerException();
- }
- if (length < 0) {
- throw new IllegalArgumentException("Length must be positive");
- }
- return hkdfSha256Expand(hkdfSha256Extract(inputKeyMaterial, salt), info, length);
- }
-
- /**
- * @return the concatenation of {@code a} and {@code b}, treating {@code null} as the empty array.
- */
- static byte[] concat(@Nullable byte[] a, @Nullable byte[] b) {
- if ((a == null) && (b == null)) {
- return new byte[] { };
- }
- if (a == null) {
- return b;
- }
- if (b == null) {
- return a;
- }
- byte[] result = new byte[a.length + b.length];
- System.arraycopy(a, 0, result, 0, a.length);
- System.arraycopy(b, 0, result, a.length, b.length);
- return result;
- }
-
- /**
- * Since {@code Arrays.copyOfRange(...)} is not available on older Android platforms,
- * a custom method for computing a subarray is provided here.
- *
- * @return the substring of {@code in} from {@code beginIndex} (inclusive)
- * up to {@code endIndex} (exclusive)
- */
- static byte[] subarray(byte[] in, int beginIndex, int endIndex) {
- if (in == null) {
- throw new NullPointerException();
- }
- int length = endIndex - beginIndex;
- if ((length < 0)
- || (beginIndex < 0)
- || (endIndex < 0)
- || (beginIndex >= in.length)
- || (endIndex > in.length)) {
- throw new IndexOutOfBoundsException();
- }
- byte[] result = new byte[length];
- if (length > 0) {
- System.arraycopy(in, beginIndex, result, 0, length);
- }
- return result;
- }
-
- /**
- * The HKDF (RFC 5869) extraction function, using the SHA-256 hash function. This function is used
- * to pre-process the inputKeyMaterial and mix it with the salt, producing output suitable for use
- * with HKDF expansion function (which produces the actual derived key).
- *
- * @see #hkdfSha256Expand(byte[], byte[], int)
- * @return HMAC-SHA256(salt, inputKeyMaterial) (salt is the "key" for the HMAC)
- * @throws InvalidKeyException if the encoded form of {@code inputKeyMaterial} cannot be accessed
- * @throws NoSuchAlgorithmException if the HmacSHA256 or AES algorithms are unavailable
- */
- private static byte[] hkdfSha256Extract(SecretKey inputKeyMaterial, byte[] salt)
- throws NoSuchAlgorithmException, InvalidKeyException {
- Mac macScheme = Mac.getInstance("HmacSHA256");
- try {
- macScheme.init(new SecretKeySpec(salt, "AES"));
- } catch (InvalidKeyException e) {
- throw new AssertionError(e); // This should never happen
- }
- // Note that the SecretKey encoding format is defined to be RAW, so the encoded form should be
- // consistent across implementations.
- byte[] encodedKeyMaterial = inputKeyMaterial.getEncoded();
- if (encodedKeyMaterial == null) {
- throw new InvalidKeyException("Cannot get encoded form of SecretKey");
- }
- return macScheme.doFinal(encodedKeyMaterial);
- }
-
- /**
- * HKDF (RFC 5869) expansion function, using the SHA-256 hash function.
- *
- * @param pseudoRandomKey should be generated by {@link #hkdfSha256Extract(SecretKey, byte[])}
- * @param info arbitrary information the derived key should be bound to
- * @param length length of the output key material in bytes
- * @return raw derived key bytes = HMAC-SHA256(pseudoRandomKey, info | 0x01)
- * @throws NoSuchAlgorithmException if the HmacSHA256 or AES algorithms are unavailable
- */
- private static byte[] hkdfSha256Expand(byte[] pseudoRandomKey, byte[] info, int length)
- throws NoSuchAlgorithmException {
- Mac macScheme = Mac.getInstance("HmacSHA256");
- try {
- macScheme.init(new SecretKeySpec(pseudoRandomKey, "AES"));
- } catch (InvalidKeyException e) {
- throw new AssertionError(e); // This should never happen
- }
-
- // Number of blocks N = ceil(hash length / output length).
- int blocks = length / 32;
- if (length % 32 > 0) {
- blocks += 1;
- }
-
- // The counter used to generate the blocks according to the RFC is only one byte long,
- // which puts a limit on the number of blocks possible.
- if (blocks > 0xFF) {
- throw new IllegalArgumentException("Maximum HKDF output length exceeded.");
- }
-
- byte[] outputBlock = new byte[32];
- byte[] counter = new byte[1];
- byte[] output = new byte[32 * blocks];
- for (int i = 0; i < blocks; ++i) {
- macScheme.reset();
- if (i > 0) {
- // Previous block
- macScheme.update(outputBlock);
- }
- // Arbitrary info
- macScheme.update(info);
- // Counter
- counter[0] = (byte) (i + 1);
- outputBlock = macScheme.doFinal(counter);
-
- System.arraycopy(outputBlock, 0, output, 32 * i, 32);
- }
-
- return subarray(output, 0, length);
- }
-
-}
diff --git a/src/main/java/com/google/security/cryptauth/lib/securemessage/PublicKeyProtoUtil.java b/src/main/java/com/google/security/cryptauth/lib/securemessage/PublicKeyProtoUtil.java
deleted file mode 100644
index 0c593fe..0000000
--- a/src/main/java/com/google/security/cryptauth/lib/securemessage/PublicKeyProtoUtil.java
+++ /dev/null
@@ -1,675 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.security.cryptauth.lib.securemessage;
-
-import com.google.common.collect.Lists;
-import com.google.protobuf.ByteString;
-import com.google.security.annotations.SuppressInsecureCipherModeCheckerPendingReview;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.DhPublicKey;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.EcP256PublicKey;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.GenericPublicKey;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.SimpleRsaPublicKey;
-import java.math.BigInteger;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.KeyFactory;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.NoSuchAlgorithmException;
-import java.security.PublicKey;
-import java.security.SecureRandom;
-import java.security.interfaces.ECPublicKey;
-import java.security.interfaces.RSAPublicKey;
-import java.security.spec.ECFieldFp;
-import java.security.spec.ECGenParameterSpec;
-import java.security.spec.ECParameterSpec;
-import java.security.spec.ECPoint;
-import java.security.spec.ECPublicKeySpec;
-import java.security.spec.InvalidKeySpecException;
-import java.security.spec.RSAPublicKeySpec;
-import javax.crypto.interfaces.DHPrivateKey;
-import javax.crypto.interfaces.DHPublicKey;
-import javax.crypto.spec.DHParameterSpec;
-import javax.crypto.spec.DHPublicKeySpec;
-
-/**
- * Utility class containing static factory methods for a simple protobuf based representation of
- * EC public keys that is intended for use with the SecureMessage library.
- *
- * N.B.: Requires the availability of an EC security provider supporting the NIST P-256 curve.
- *
- */
-public class PublicKeyProtoUtil {
-
- private PublicKeyProtoUtil() {} // Do not instantiate
-
- /**
- * Caches state about whether the current platform supports Elliptic Curve algorithms.
- */
- private static final Boolean IS_LEGACY_CRYPTO_REQUIRED = determineIfLegacyCryptoRequired();
-
- private static final BigInteger ONE = new BigInteger("1");
- private static final BigInteger TWO = new BigInteger("2");
-
- /**
- * Name for Elliptic Curve cryptography algorithm suite, used by the security provider. If the
- * security provider does not implement the specified algorithm, runtime errors will ensue.
- */
- private static final String EC_ALG = "EC";
-
- /**
- * A common name for the NIST P-256 curve, used by most Java security providers.
- */
- private static final String EC_P256_COMMON_NAME = "secp256r1";
-
- /**
- * A name the NIST P-256 curve, used by the OpenSSL Java security provider (e.g,. on Android).
- */
- private static final String EC_P256_OPENSSL_NAME = "prime256v1";
-
- /**
- * The {@link ECParameterSpec} for the NIST P-256 Elliptic Curve.
- */
- private static final ECParameterSpec EC_P256_PARAMS = isLegacyCryptoRequired() ? null :
- ((ECPublicKey) generateEcP256KeyPair().getPublic()).getParams();
-
- /**
- * The prime {@code p} describing the field for the NIST P-256 curve.
- */
- private static final BigInteger EC_P256_P = isLegacyCryptoRequired() ? null :
- ((ECFieldFp) EC_P256_PARAMS.getCurve().getField()).getP();
-
- /**
- * The coefficient {@code a} for the NIST P-256 curve.
- */
- private static final BigInteger EC_P256_A = isLegacyCryptoRequired() ? null :
- EC_P256_PARAMS.getCurve().getA();
-
- /**
- * The coefficient {@code b} for the NIST P-256 curve.
- */
- private static final BigInteger EC_P256_B = isLegacyCryptoRequired() ? null :
- EC_P256_PARAMS.getCurve().getB();
-
- /**
- * Maximum number of bytes in a 2's complement encoding of a NIST P-256 elliptic curve point.
- */
- private static final int MAX_P256_ENCODING_BYTES = 33;
-
- /**
- * The JCA name for the RSA cryptography suite.
- */
- private static final String RSA_ALG = "RSA";
-
- private static final int RSA2048_MODULUS_BITS = 2048;
-
- /**
- * Maximum number of bytes in a 2's complement encoding of a 2048-bit RSA key.
- */
- private static final int MAX_RSA2048_ENCODING_BYTES = 257;
-
- /**
- * The JCA name for the Diffie-Hellman cryptography suite.
- */
- private static final String DH_ALG = "DH";
-
- /**
- * The prime from the 2048-bit MODP Group (group 14) described in RFC 3526, to be used for
- * Diffie-Hellman computations. Use only if Elliptic Curve ciphers are unavailable.
- */
- public static final BigInteger DH_P = new BigInteger(
- "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" +
- "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" +
- "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" +
- "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" +
- "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" +
- "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" +
- "83655D23DCA3AD961C62F356208552BB9ED529077096966D" +
- "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" +
- "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" +
- "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" +
- "15728E5A8AACAA68FFFFFFFFFFFFFFFF", 16);
-
- /**
- * The generator for the 2048-bit MODP Group (group 14) described in RFC 3526, to be used for
- * Diffie-Hellman computations. Use only if Elliptic Curve ciphers are unavailable.
- */
- public static final BigInteger DH_G = TWO;
-
- /**
- * The size of the Diffie-Hellman exponent to use, in bits.
- */
- public static final int DH_LEN = 512;
-
- /**
- * Maximum number of bytes in a 2's complement encoding of a
- * Diffie-Hellman key using {@link #DH_G}.
- */
- private static final int MAX_DH2048_ENCODING_BYTES = 257;
-
- /**
- * Version code for the Honeycomb release of Android, which is the first release supporting
- * Elliptic Curve.
- */
- public static final int ANDROID_HONEYCOMB_SDK_INT = 11;
-
- /**
- * Encodes any supported {@link PublicKey} type as a {@link GenericPublicKey} proto message.
- *
- * @see SecureMessageProto constants (defined in the .proto file) for supported types
- */
- public static GenericPublicKey encodePublicKey(PublicKey pk) {
- if (pk == null) {
- throw new NullPointerException();
- }
- if (pk instanceof ECPublicKey) {
- return GenericPublicKey.newBuilder()
- .setType(SecureMessageProto.PublicKeyType.EC_P256)
- .setEcP256PublicKey(encodeEcPublicKey(pk))
- .build();
- }
- if (pk instanceof RSAPublicKey) {
- return GenericPublicKey.newBuilder()
- .setType(SecureMessageProto.PublicKeyType.RSA2048)
- .setRsa2048PublicKey(encodeRsa2048PublicKey(pk))
- .build();
- }
- if (pk instanceof DHPublicKey) {
- return GenericPublicKey.newBuilder()
- .setType(SecureMessageProto.PublicKeyType.DH2048_MODP)
- .setDh2048PublicKey(encodeDh2048PublicKey(pk))
- .build();
- }
- throw new IllegalArgumentException("Unsupported PublicKey type");
- }
-
- /**
- * Encodes an {@link ECPublicKey} to an {@link GenericPublicKey} proto message. The returned key
- * has a null-byte padded to the front in order to match the C++ implementation.
- */
- public static GenericPublicKey encodePaddedEcPublicKey(PublicKey pk) {
- if (pk == null) {
- throw new NullPointerException();
- }
- if (!(pk instanceof ECPublicKey)) {
- throw new IllegalArgumentException("Expected ECPublicKey PublicKey type");
- }
-
- ECPublicKey epk = pkToECPublicKey(pk);
- ByteString nullByteString = ByteString.copyFrom(new byte[] {0});
- ByteString xByteString = extractX(epk);
- if (xByteString.size() < MAX_P256_ENCODING_BYTES) {
- xByteString = ByteString.copyFrom(Lists.newArrayList(nullByteString, xByteString));
- }
- ByteString yByteString = extractY(epk);
- if (yByteString.size() < MAX_P256_ENCODING_BYTES) {
- yByteString = ByteString.copyFrom(Lists.newArrayList(nullByteString, yByteString));
- }
- EcP256PublicKey newKey =
- EcP256PublicKey.newBuilder().setX(xByteString).setY(yByteString).build();
-
- return GenericPublicKey.newBuilder()
- .setType(SecureMessageProto.PublicKeyType.EC_P256)
- .setEcP256PublicKey(newKey)
- .build();
- }
-
- /**
- * Encodes an {@link ECPublicKey} to an {@link EcP256PublicKey} proto message.
- */
- public static EcP256PublicKey encodeEcPublicKey(PublicKey pk) {
- ECPublicKey epk = pkToECPublicKey(pk);
- return EcP256PublicKey.newBuilder()
- .setX(extractX(epk))
- .setY(extractY(epk))
- .build();
- }
-
- /**
- * Encodes a 2048-bit {@link RSAPublicKey} to an {@link SimpleRsaPublicKey} proto message.
- */
- public static SimpleRsaPublicKey encodeRsa2048PublicKey(PublicKey pk) {
- RSAPublicKey rpk = pkToRSAPublicKey(pk);
- return SimpleRsaPublicKey.newBuilder()
- .setN(ByteString.copyFrom(rpk.getModulus().toByteArray()))
- .setE(rpk.getPublicExponent().intValue())
- .build();
- }
-
- /**
- * Encodes a 2048-bit {@link DhPublicKey} using the {@link #DH_G} group to a
- * {@link DhPublicKey} proto message.
- */
- public static DhPublicKey encodeDh2048PublicKey(PublicKey pk) {
- DHPublicKey dhpk = pkToDHPublicKey(pk);
- return DhPublicKey.newBuilder()
- .setY(ByteString.copyFrom(dhpk.getY().toByteArray()))
- .build();
- }
-
- /**
- * Extracts a {@link PublicKey} from an {@link GenericPublicKey} proto message.
- *
- * @throws InvalidKeySpecException if the input is not a valid and/or supported public key type
- */
- public static PublicKey parsePublicKey(GenericPublicKey gpk) throws InvalidKeySpecException {
- if (!gpk.hasType()) {
- // "required" means nothing in micro proto land. We have to check this ourselves.
- throw new InvalidKeySpecException("GenericPublicKey.type is a required field");
- }
- switch (gpk.getType()) {
- case EC_P256:
- if (!gpk.hasEcP256PublicKey()) {
- break;
- }
- return parseEcPublicKey(gpk.getEcP256PublicKey());
- case RSA2048:
- if (!gpk.hasRsa2048PublicKey()) {
- break;
- }
- return parseRsa2048PublicKey(gpk.getRsa2048PublicKey());
- case DH2048_MODP:
- if (!gpk.hasDh2048PublicKey()) {
- break;
- }
- return parseDh2048PublicKey(gpk.getDh2048PublicKey());
- default:
- throw new InvalidKeySpecException("Unsupported GenericPublicKey type: " + gpk.getType());
- }
- throw new InvalidKeySpecException("key object is missing for key type: " + gpk.getType());
- }
-
- /**
- * Extracts a {@link ECPublicKey} from an {@link EcP256PublicKey} proto message.
- *
- * @throws InvalidKeySpecException if the input is not a valid NIST P-256 public key or if
- * this platform does not support Elliptic Curve keys
- */
- public static ECPublicKey parseEcPublicKey(EcP256PublicKey p256pk)
- throws InvalidKeySpecException {
- if (!p256pk.hasX() || !p256pk.hasY()) {
- throw new InvalidKeySpecException("Key is missing a required coordinate");
- }
- if (isLegacyCryptoRequired()) {
- throw new InvalidKeySpecException("Elliptic Curve keys not supported on this platform");
- }
- byte[] encodedX = p256pk.getX().toByteArray();
- byte[] encodedY = p256pk.getY().toByteArray();
- try {
- validateEcP256CoordinateEncoding(encodedX);
- validateEcP256CoordinateEncoding(encodedY);
- BigInteger wX = new BigInteger(encodedX);
- BigInteger wY = new BigInteger(encodedY);
- validateEcP256CurvePoint(wX, wY);
- return (ECPublicKey) KeyFactory.getInstance(EC_ALG).generatePublic(
- new ECPublicKeySpec(new ECPoint(wX, wY), EC_P256_PARAMS));
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Extracts a {@link RSAPublicKey} from an {@link SimpleRsaPublicKey} proto message.
- *
- * @throws InvalidKeySpecException when the input RSA public key is invalid
- */
- public static RSAPublicKey parseRsa2048PublicKey(SimpleRsaPublicKey pk)
- throws InvalidKeySpecException {
- if (!pk.hasN()) {
- throw new InvalidKeySpecException("required field is missing");
- }
- byte[] encodedN = pk.getN().toByteArray();
- validateSimpleRsaEncoding(encodedN);
- BigInteger n = new BigInteger(encodedN);
- if (n.bitLength() != RSA2048_MODULUS_BITS) {
- throw new InvalidKeySpecException();
- }
- BigInteger e = BigInteger.valueOf(pk.getE());
- try {
- return (RSAPublicKey) KeyFactory.getInstance(RSA_ALG).generatePublic(
- new RSAPublicKeySpec(n, e));
- } catch (NoSuchAlgorithmException e1) {
- throw new AssertionError(e1); // Should never happen
- }
- }
-
- /**
- * Extracts a {@link DHPublicKey} from an {@link DhPublicKey} proto message.
- *
- * @throws InvalidKeySpecException when the input DH public key is invalid
- */
- @SuppressInsecureCipherModeCheckerPendingReview // b/32143855
- public static DHPublicKey parseDh2048PublicKey(DhPublicKey pk) throws InvalidKeySpecException {
- if (!pk.hasY()) {
- throw new InvalidKeySpecException("required field is missing");
- }
- byte[] encodedY = pk.getY().toByteArray();
- validateDhEncoding(encodedY);
- BigInteger y;
- try {
- y = new BigInteger(encodedY);
- } catch (NumberFormatException e) {
- throw new InvalidKeySpecException();
- }
- validateDhGroupElement(y);
- try {
- return (DHPublicKey) KeyFactory.getInstance(DH_ALG).generatePublic(
- new DHPublicKeySpec(y, DH_P, DH_G));
- } catch (NoSuchAlgorithmException e) {
- throw new AssertionError(e); // Should never happen
- }
- }
-
- /**
- * @return a freshly generated NIST P-256 Elliptic Curve key pair.
- */
- public static KeyPair generateEcP256KeyPair() {
- return getEcKeyGen().generateKeyPair();
- }
-
- /**
- * @return a freshly generated 2048-bit RSA key pair.
- */
- public static KeyPair generateRSA2048KeyPair() {
- return getRsaKeyGen().generateKeyPair();
- }
-
- /**
- * @return a freshly generated Diffie-Hellman key pair for the 2048-bit group
- * described by {@link #DH_G}
- */
- public static KeyPair generateDh2048KeyPair() {
- try {
- return getDhKeyGen().generateKeyPair();
- } catch (InvalidAlgorithmParameterException e) {
- // Construct an appropriate KeyPair manually, since this platform refuses to do it for us
- DHParameterSpec spec = new DHParameterSpec(DH_P, DH_G);
- BigInteger x = new BigInteger(DH_LEN, new SecureRandom());
- DHPrivateKey privateKey = new DHPrivateKeyShim(x, spec);
- DHPublicKey publicKey = new DHPublicKeyShim(DH_G.modPow(x, DH_P), spec);
- return new KeyPair(publicKey, privateKey);
- }
- }
-
- /**
- * A lightweight encoding for a {@link DHPrivateKey}. Strongly recommended over attempting to use
- * {@link DHPrivateKey#getEncoded()}, but not compatible with the standard encoding.
- *
- * @see #parseDh2048PrivateKey(byte[])
- */
- public static byte[] encodeDh2048PrivateKey(DHPrivateKey sk) {
- return sk.getX().toByteArray();
- }
-
- /**
- * Parses a {@link DHPrivateKey} encoded with {@link #encodeDh2048PrivateKey(DHPrivateKey)}.
- */
- public static DHPrivateKey parseDh2048PrivateKey(byte[] encodedX)
- throws InvalidKeySpecException {
- validateDhEncoding(encodedX); // Could be stricter for x, but should be fine to use this
- BigInteger x;
- try {
- x = new BigInteger(encodedX);
- } catch (NumberFormatException e) {
- throw new InvalidKeySpecException();
- }
- validateDhGroupElement(x); // Again, this validation should be good enough
- return new DHPrivateKeyShim(x, new DHParameterSpec(DH_P, DH_G));
- }
-
- /**
- * @throws InvalidKeySpecException if point ({@code x},{@code y}) isn't on the NIST P-256 curve
- */
- private static void validateEcP256CurvePoint(BigInteger x, BigInteger y)
- throws InvalidKeySpecException {
- if ((x.signum() == -1) || (y.signum() == -1)) {
- throw new InvalidKeySpecException("Point encoding must use only non-negative integers");
- }
-
- BigInteger p = EC_P256_P;
- if ((x.compareTo(p) >= 0) || (y.compareTo(p) >= 0)) {
- throw new InvalidKeySpecException("Point lies outside of the expected field");
- }
-
- // Points on the curve satisfy y^2 = x^3 + ax + b (mod p)
- BigInteger lhs = squareMod(y, p);
- BigInteger rhs = squareMod(x, p).add(EC_P256_A) // = (x^2 + a)
- .multiply(x).mod(p) // = x(x^2 + a) = x^3 + ax
- .add(EC_P256_B) // = x^3 + ax + b
- .mod(p);
- if (!lhs.equals(rhs)) {
- throw new InvalidKeySpecException("Point does not lie on the expected curve");
- }
- }
-
- /**
- * @return value of {@code x}^2 (mod {@code p})
- */
- private static BigInteger squareMod(BigInteger x, BigInteger p) {
- return x.multiply(x).mod(p);
- }
-
- /**
- * @throws InvalidKeySpecException if the coordinate is too large for a 256-bit curve
- */
- private static void validateEcP256CoordinateEncoding(byte[] p) throws InvalidKeySpecException {
- if ((p.length == 0)
- || (p.length > MAX_P256_ENCODING_BYTES)
- || (p.length == MAX_P256_ENCODING_BYTES && p[0] != 0)) {
- throw new InvalidKeySpecException(); // Intentionally vague for security reasons
- }
- }
-
- /**
- * @throws InvalidKeySpecException if the input is too large for a 2048-bit RSA modulus
- */
- private static void validateSimpleRsaEncoding(byte[] n) throws InvalidKeySpecException {
- if (n.length == 0 || n.length > MAX_RSA2048_ENCODING_BYTES) {
- throw new InvalidKeySpecException();
- }
- }
-
- /**
- * @throws InvalidKeySpecException if the public key is too large for a 2048-bit DH group
- */
- private static void validateDhEncoding(byte[] y) throws InvalidKeySpecException {
- if (y.length == 0 || y.length > MAX_DH2048_ENCODING_BYTES) {
- throw new InvalidKeySpecException();
- }
- }
-
- /**
- * @throws InvalidKeySpecException if {@code y} is not a valid Diffie-Hellman public key
- */
- private static void validateDhGroupElement(BigInteger y) throws InvalidKeySpecException {
- // Check that 1 < y < p -1
- if ((y.compareTo(ONE) < 1) || (y.compareTo(DH_P.subtract(ONE)) > -1)) {
- throw new InvalidKeySpecException();
- }
- }
-
- private static ByteString extractY(ECPublicKey epk) {
- return ByteString.copyFrom(epk.getW().getAffineY().toByteArray());
- }
-
- private static ByteString extractX(ECPublicKey epk) {
- return ByteString.copyFrom(epk.getW().getAffineX().toByteArray());
- }
-
- private static ECPublicKey pkToECPublicKey(PublicKey pk) {
- if (pk == null) {
- throw new NullPointerException();
- }
- if (!(pk instanceof ECPublicKey)) {
- throw new IllegalArgumentException("Not an EC Public Key");
- }
- return (ECPublicKey) pk;
- }
-
- private static RSAPublicKey pkToRSAPublicKey(PublicKey pk) {
- if (pk == null) {
- throw new NullPointerException();
- }
- if (!(pk instanceof RSAPublicKey)) {
- throw new IllegalArgumentException("Not an RSA Public Key");
- }
- return (RSAPublicKey) pk;
- }
-
- private static DHPublicKey pkToDHPublicKey(PublicKey pk) {
- if (pk == null) {
- throw new NullPointerException();
- }
- if (!(pk instanceof DHPublicKey)) {
- throw new IllegalArgumentException("Not a DH Public Key");
- }
- return (DHPublicKey) pk;
- }
-
- /**
- * @return an EC {@link KeyPairGenerator} object initialized for NIST P-256.
- */
- private static KeyPairGenerator getEcKeyGen() {
- KeyPairGenerator keygen;
- try {
- keygen = KeyPairGenerator.getInstance(EC_ALG);
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException(e);
- }
- try {
- // Try using the OpenSSL provider first, since we prefer it over BouncyCastle
- keygen.initialize(new ECGenParameterSpec(EC_P256_OPENSSL_NAME));
- return keygen;
- } catch (InvalidAlgorithmParameterException e) {
- // Try another name for NIST P-256
- }
- try {
- keygen.initialize(new ECGenParameterSpec(EC_P256_COMMON_NAME));
- return keygen;
- } catch (InvalidAlgorithmParameterException e) {
- throw new RuntimeException("Unable to find the NIST P-256 curve");
- }
- }
-
- /**
- * @return an RSA {@link KeyPairGenerator} object initialized for 2048-bit keys.
- */
- private static KeyPairGenerator getRsaKeyGen() {
- try {
- KeyPairGenerator keygen = KeyPairGenerator.getInstance(RSA_ALG);
- keygen.initialize(RSA2048_MODULUS_BITS);
- return keygen;
- } catch (NoSuchAlgorithmException e) {
- throw new AssertionError(e); // This should never happen
- }
- }
-
- /**
- * @return a DH {@link KeyPairGenerator} object initialized for the group described by {@link
- * #DH_G}.
- * @throws InvalidAlgorithmParameterException on some platforms that don't support large DH groups
- */
- @SuppressInsecureCipherModeCheckerPendingReview // b/32143855
- private static KeyPairGenerator getDhKeyGen() throws InvalidAlgorithmParameterException {
- try {
- KeyPairGenerator keygen = KeyPairGenerator.getInstance(DH_ALG);
- keygen.initialize(new DHParameterSpec(DH_P, DH_G, DH_LEN));
- return keygen;
- } catch (NoSuchAlgorithmException e) {
- throw new AssertionError(e); // This should never happen
- }
- }
-
- /**
- * A lightweight shim class to enable the creation of {@link DHPublicKey} and {@link DHPrivateKey}
- * objects that accept arbitrary {@link DHParameterSpec}s -- unfortunately, many platforms do
- * not support using reasonably sized Diffie-Hellman groups any other way. For instance, see
- * <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6521495">Java bug 6521495</a>.
- */
- public abstract static class DHKeyShim {
-
- private BigInteger eitherXorY;
- private DHParameterSpec params;
-
- public DHKeyShim(BigInteger eitherXorY, DHParameterSpec params) {
- this.eitherXorY = eitherXorY;
- this.params = params;
- }
-
- public DHParameterSpec getParams() {
- return params;
- }
-
- public String getAlgorithm() {
- return "DH";
- }
-
- public String getFormat() {
- return null;
- }
-
- public byte[] getEncoded() {
- return null;
- }
-
- public BigInteger getX() {
- return eitherXorY;
- }
-
- public BigInteger getY() {
- return eitherXorY;
- }
- }
-
- /**
- * A simple {@link DHPublicKey} implementation.
- *
- * @see DHKeyShim
- */
- public static class DHPublicKeyShim extends DHKeyShim implements DHPublicKey {
- public DHPublicKeyShim(BigInteger y, DHParameterSpec params) {
- super(y, params);
- }
- }
-
- /**
- * A simple {@link DHPrivateKey} implementation.
- *
- * @see DHKeyShim
- */
- public static class DHPrivateKeyShim extends DHKeyShim implements DHPrivateKey {
- public DHPrivateKeyShim(BigInteger x, DHParameterSpec params) {
- super(x, params);
- }
- }
-
- /**
- * @return true if this platform does not support Elliptic Curve algorithms
- */
- public static boolean isLegacyCryptoRequired() {
- return IS_LEGACY_CRYPTO_REQUIRED;
- }
-
- /**
- * @return true if using the Elliptic Curve key generator fails on this platform
- */
- private static boolean determineIfLegacyCryptoRequired() {
- try {
- getEcKeyGen();
- } catch (Exception e) {
- return true;
- }
- return false;
- }
-}
diff --git a/src/main/java/com/google/security/cryptauth/lib/securemessage/SecureMessageBuilder.java b/src/main/java/com/google/security/cryptauth/lib/securemessage/SecureMessageBuilder.java
deleted file mode 100644
index f1a9464..0000000
--- a/src/main/java/com/google/security/cryptauth/lib/securemessage/SecureMessageBuilder.java
+++ /dev/null
@@ -1,277 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.security.cryptauth.lib.securemessage;
-
-import com.google.protobuf.ByteString;
-import com.google.security.cryptauth.lib.securemessage.CryptoOps.EncType;
-import com.google.security.cryptauth.lib.securemessage.CryptoOps.SigType;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.Header;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.HeaderAndBodyInternal;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.SecureMessage;
-import java.security.InvalidKeyException;
-import java.security.Key;
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
-import java.util.Arrays;
-import javax.annotation.Nullable;
-
-/**
- * Builder for {@link SecureMessage} protos. Can be used to create either signed messages,
- * or "signcrypted" (encrypted then signed) messages that include a tight binding between the
- * ciphertext portion and a verification key identity.
- *
- * @see SecureMessageParser
- */
-public class SecureMessageBuilder {
- private ByteString publicMetadata;
- private ByteString verificationKeyId;
- private ByteString decryptionKeyId;
- /**
- * This data is never sent inside the protobufs, so the builder just saves it as a byte[].
- */
- private byte[] associatedData;
-
- private SecureRandom rng;
-
- public SecureMessageBuilder() {
- reset();
- this.rng = new SecureRandom();
- }
-
- /**
- * Resets this {@link SecureMessageBuilder} instance to a blank configuration (and returns it).
- */
- public SecureMessageBuilder reset() {
- this.publicMetadata = null;
- this.verificationKeyId = null;
- this.decryptionKeyId = null;
- this.associatedData = null;
- return this;
- }
-
- /**
- * Optional metadata to be sent along with the header information in this {@link SecureMessage}.
- * <p>
- * Note that this value will be sent <em>UNENCRYPTED</em> in all cases.
- * <p>
- * Can be used with either cleartext or signcrypted messages, but is intended primarily for use
- * with signcrypted messages.
- */
- public SecureMessageBuilder setPublicMetadata(byte[] publicMetadata) {
- this.publicMetadata = ByteString.copyFrom(publicMetadata);
- return this;
- }
-
- /**
- * The recipient of the {@link SecureMessage} should be able to uniquely determine the correct
- * verification key, given only this value.
- * <p>
- * Can be used with either cleartext or signcrypted messages. Setting this is mandatory for
- * signcrypted messages using a public key {@link SigType}, in order to bind the encrypted
- * body to a specific verification key.
- * <p>
- * Note that this value is sent <em>UNENCRYPTED</em> in all cases.
- */
- public SecureMessageBuilder setVerificationKeyId(byte[] verificationKeyId) {
- this.verificationKeyId = ByteString.copyFrom(verificationKeyId);
- return this;
- }
-
- /**
- * To be used only with {@link #buildSignCryptedMessage(Key, SigType, Key, EncType, byte[])},
- * this value is sent <em>UNENCRYPTED</em> as part of the header. It should be used by the
- * recipient of the {@link SecureMessage} to identify an appropriate key to use for decrypting
- * the message body.
- */
- public SecureMessageBuilder setDecryptionKeyId(byte[] decryptionKeyId) {
- this.decryptionKeyId = ByteString.copyFrom(decryptionKeyId);
- return this;
- }
-
- /**
- * Additional data is "associated" with this {@link SecureMessage}, but will not be sent as
- * part of it. The recipient of the {@link SecureMessage} will need to provide the same data in
- * order to verify the message body. Setting this to {@code null} is equivalent to using an
- * empty array (unlike the behavior of {@code VerificationKeyId} and {@code DecryptionKeyId}).
- * <p>
- * Note that the <em>size</em> (length in bytes) of the associated data will be sent in the
- * <em>UNENCRYPTED</em> header information, even if you are using encryption.
- * <p>
- * If you will be using {@link #buildSignedCleartextMessage(Key, SigType, byte[])}, then anyone
- * observing the {@link SecureMessage} may be able to infer this associated data via an
- * "offline dictionary attack". That is, when no encryption is used, you will not be hiding this
- * data simply because it is not being sent over the wire.
- */
- public SecureMessageBuilder setAssociatedData(@Nullable byte[] associatedData) {
- this.associatedData = associatedData;
- return this;
- }
-
- // @VisibleForTesting
- SecureMessageBuilder setRng(SecureRandom rng) {
- this.rng = rng;
- return this;
- }
-
- /**
- * Generates a signed {@link SecureMessage} with the payload {@code body} left
- * <em>UNENCRYPTED</em>.
- *
- * <p>Note that if you have used {@link #setAssociatedData(byte[])}, the associated data will
- * be subject to offline dictionary attacks if you use a public key {@link SigType}.
- *
- * <p>Doesn't currently support symmetric keys stored in a TPM (since we access the raw key).
- *
- * @see SecureMessageParser#parseSignedCleartextMessage(SecureMessage, Key, SigType)
- */
- public SecureMessage buildSignedCleartextMessage(Key signingKey, SigType sigType, byte[] body)
- throws NoSuchAlgorithmException, InvalidKeyException {
- if ((signingKey == null) || (sigType == null) || (body == null)) {
- throw new NullPointerException();
- }
- if (decryptionKeyId != null) {
- throw new IllegalStateException("Cannot set decryptionKeyId for a cleartext message");
- }
-
- byte[] headerAndBody = serializeHeaderAndBody(
- buildHeader(sigType, EncType.NONE, null).toByteArray(), body);
- return createSignedResult(signingKey, sigType, headerAndBody, associatedData);
- }
-
- /**
- * Generates a signed and encrypted {@link SecureMessage}. If the signature type requires a public
- * key, such as with ECDSA_P256_SHA256, then the caller <em>must</em> set a verification id using
- * the {@link #setVerificationKeyId(byte[])} method. The verification key id will be bound to the
- * encrypted {@code body}, preventing attacks that involve stripping the signature and then
- * re-signing the encrypted {@code body} as if it was originally sent by the attacker.
- *
- * <p>
- * It is safe to re-use one {@link javax.crypto.SecretKey} as both {@code signingKey} and
- * {@code encryptionKey}, even if that key is also used for
- * {@link #buildSignedCleartextMessage(Key, SigType, byte[])}. In fact, the resulting output
- * encoding will be more compact when the same symmetric key is used for both.
- *
- * <p>
- * Note that PublicMetadata and other header fields are left <em>UNENCRYPTED</em>.
- *
- * <p>
- * Doesn't currently support symmetric keys stored in a TPM (since we access the raw key).
- *
- * @param encType <em>must not</em> be set to {@link EncType#NONE}
- * @see SecureMessageParser#parseSignCryptedMessage(SecureMessage, Key, SigType, Key, EncType)
- */
- public SecureMessage buildSignCryptedMessage(
- Key signingKey, SigType sigType, Key encryptionKey, EncType encType, byte[] body)
- throws NoSuchAlgorithmException, InvalidKeyException {
- if ((signingKey == null)
- || (sigType == null)
- || (encryptionKey == null)
- || (encType == null)
- || (body == null)) {
- throw new NullPointerException();
- }
- if (encType == EncType.NONE) {
- throw new IllegalArgumentException(encType + " not supported for encrypted messages");
- }
- if (sigType.isPublicKeyScheme() && (verificationKeyId == null)) {
- throw new IllegalStateException(
- "Must set a verificationKeyId when using public key signature with encryption");
- }
-
- byte[] iv = CryptoOps.generateIv(encType, rng);
- byte[] header = buildHeader(sigType, encType, iv).toByteArray();
-
- // We may or may not need an extra tag in front of the plaintext body
- byte[] taggedBody;
- // We will only sign the associated data when we don't tag the plaintext body
- byte[] associatedDataToBeSigned;
- if (taggedPlaintextRequired(signingKey, sigType, encryptionKey)) {
- // Place a "tag" in front of the the plaintext message containing a digest of the header
- taggedBody = CryptoOps.concat(
- // Digest the header + any associated data, yielding a tag to be encrypted with the body.
- CryptoOps.digest(CryptoOps.concat(header, associatedData)),
- body);
- associatedDataToBeSigned = null; // We already handled any associatedData via the tag
- } else {
- taggedBody = body;
- associatedDataToBeSigned = associatedData;
- }
-
- // Compute the encrypted body, which binds the tag to the message inside the ciphertext
- byte[] encryptedBody = CryptoOps.encrypt(encryptionKey, encType, rng, iv, taggedBody);
-
- byte[] headerAndBody = serializeHeaderAndBody(header, encryptedBody);
- return createSignedResult(signingKey, sigType, headerAndBody, associatedDataToBeSigned);
- }
-
- /**
- * Indicates whether a "tag" is needed next to the plaintext body inside the ciphertext, to
- * prevent the same ciphertext from being reused with someone else's signature on it.
- */
- static boolean taggedPlaintextRequired(Key signingKey, SigType sigType, Key encryptionKey) {
- // We need a tag if different keys are being used to "sign" vs. encrypt
- return sigType.isPublicKeyScheme()
- || !Arrays.equals(signingKey.getEncoded(), encryptionKey.getEncoded());
- }
-
- /**
- * @param iv IV or {@code null} if IV to be left unset in the Header
- */
- private Header buildHeader(SigType sigType, EncType encType, byte[] iv) {
- Header.Builder result = Header.newBuilder()
- .setSignatureScheme(sigType.getSigScheme())
- .setEncryptionScheme(encType.getEncScheme());
- if (verificationKeyId != null) {
- result.setVerificationKeyId(verificationKeyId);
- }
- if (decryptionKeyId != null) {
- result.setDecryptionKeyId(decryptionKeyId);
- }
- if (publicMetadata != null) {
- result.setPublicMetadata(publicMetadata);
- }
- if (associatedData != null) {
- result.setAssociatedDataLength(associatedData.length);
- }
- if (iv != null) {
- result.setIv(ByteString.copyFrom(iv));
- }
- return result.build();
- }
-
- /**
- * @param header a serialized representation of a {@link Header}
- * @param body arbitrary payload data
- * @return a serialized representation of a {@link SecureMessageProto.HeaderAndBody}
- */
- private byte[] serializeHeaderAndBody(byte[] header, byte[] body) {
- return HeaderAndBodyInternal.newBuilder()
- .setHeader(ByteString.copyFrom(header))
- .setBody(ByteString.copyFrom(body))
- .build()
- .toByteArray();
- }
-
- private SecureMessage createSignedResult(
- Key signingKey, SigType sigType, byte[] headerAndBody, @Nullable byte[] associatedData)
- throws NoSuchAlgorithmException, InvalidKeyException {
- byte[] sig =
- CryptoOps.sign(sigType, signingKey, rng, CryptoOps.concat(headerAndBody, associatedData));
- return SecureMessage.newBuilder()
- .setHeaderAndBody(ByteString.copyFrom(headerAndBody))
- .setSignature(ByteString.copyFrom(sig))
- .build();
- }
-}
diff --git a/src/main/java/com/google/security/cryptauth/lib/securemessage/SecureMessageParser.java b/src/main/java/com/google/security/cryptauth/lib/securemessage/SecureMessageParser.java
deleted file mode 100644
index d634d40..0000000
--- a/src/main/java/com/google/security/cryptauth/lib/securemessage/SecureMessageParser.java
+++ /dev/null
@@ -1,270 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.security.cryptauth.lib.securemessage;
-
-import com.google.protobuf.ByteString;
-import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.security.cryptauth.lib.securemessage.CryptoOps.EncType;
-import com.google.security.cryptauth.lib.securemessage.CryptoOps.SigType;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.Header;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.HeaderAndBody;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.HeaderAndBodyInternal;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.SecureMessage;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.Key;
-import java.security.NoSuchAlgorithmException;
-import java.security.SignatureException;
-import javax.annotation.Nullable;
-import javax.crypto.BadPaddingException;
-import javax.crypto.IllegalBlockSizeException;
-
-/**
- * Utility class to parse and verify {@link SecureMessage} protos. Verifies the signature on the
- * message, and decrypts "signcrypted" messages (while simultaneously verifying the signature).
- *
- * @see SecureMessageBuilder
- */
-public class SecureMessageParser {
-
- private SecureMessageParser() {} // Do not instantiate
-
- /**
- * Extracts the {@link Header} component from a {@link SecureMessage} but <em>DOES NOT VERIFY</em>
- * the signature when doing so. Callers should not trust the resulting output until after a
- * subsequent {@code parse*()} call has succeeded.
- *
- * <p>The intention is to allow the caller to determine the type of the protocol message and which
- * keys are in use, prior to attempting to verify (and possibly decrypt) the payload body.
- */
- public static Header getUnverifiedHeader(SecureMessage secmsg)
- throws InvalidProtocolBufferException {
- if (!secmsg.hasHeaderAndBody()) {
- throw new InvalidProtocolBufferException("Missing header and body");
- }
- if (!HeaderAndBody.parseFrom(secmsg.getHeaderAndBody()).hasHeader()) {
- throw new InvalidProtocolBufferException("Missing header");
- }
- Header result = HeaderAndBody.parseFrom(secmsg.getHeaderAndBody()).getHeader();
- // Check that at least a signature scheme was set
- if (!result.hasSignatureScheme()) {
- throw new InvalidProtocolBufferException("Missing header field(s)");
- }
- // Check signature scheme is legal
- try {
- SigType.valueOf(result.getSignatureScheme());
- } catch (IllegalArgumentException e) {
- throw new InvalidProtocolBufferException("Corrupt/unsupported SignatureScheme");
- }
- // Check encryption scheme is legal
- if (result.hasEncryptionScheme()) {
- try {
- EncType.valueOf(result.getEncryptionScheme());
- } catch (IllegalArgumentException e) {
- throw new InvalidProtocolBufferException("Corrupt/unsupported EncryptionScheme");
- }
- }
- return result;
- }
-
- /**
- * Parses a {@link SecureMessage} containing a cleartext payload body, and verifies the signature.
- *
- * @return the parsed {@link HeaderAndBody} pair (which is fully verified)
- * @throws SignatureException if signature verification fails
- * @see SecureMessageBuilder#buildSignedCleartextMessage(Key, SigType, byte[])
- */
- public static HeaderAndBody parseSignedCleartextMessage(
- SecureMessage secmsg, Key verificationKey, SigType sigType)
- throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
- return parseSignedCleartextMessage(secmsg, verificationKey, sigType, null);
- }
-
- /**
- * Parses a {@link SecureMessage} containing a cleartext payload body, and verifies the signature.
- *
- * @param associatedData optional associated data bound to the signature (but not in the message)
- * @return the parsed {@link HeaderAndBody} pair (which is fully verified)
- * @throws SignatureException if signature verification fails
- * @see SecureMessageBuilder#buildSignedCleartextMessage(Key, SigType, byte[])
- */
- public static HeaderAndBody parseSignedCleartextMessage(
- SecureMessage secmsg, Key verificationKey, SigType sigType, @Nullable byte[] associatedData)
- throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
- if ((secmsg == null) || (verificationKey == null) || (sigType == null)) {
- throw new NullPointerException();
- }
- return verifyHeaderAndBody(
- secmsg,
- verificationKey,
- sigType,
- EncType.NONE,
- associatedData,
- false /* suppressAssociatedData is always false for signed cleartext */);
- }
-
- /**
- * Parses a {@link SecureMessage} containing an encrypted payload body, extracting a decryption of
- * the payload body and verifying the signature.
- *
- * @return the parsed {@link HeaderAndBody} pair (which is fully verified and decrypted)
- * @throws SignatureException if signature verification fails
- * @see SecureMessageBuilder#buildSignCryptedMessage(Key, SigType, Key, EncType, byte[])
- */
- public static HeaderAndBody parseSignCryptedMessage(
- SecureMessage secmsg,
- Key verificationKey,
- SigType sigType,
- Key decryptionKey,
- EncType encType)
- throws InvalidKeyException, NoSuchAlgorithmException, SignatureException {
- return parseSignCryptedMessage(secmsg, verificationKey, sigType, decryptionKey, encType, null);
- }
-
- /**
- * Parses a {@link SecureMessage} containing an encrypted payload body, extracting a decryption of
- * the payload body and verifying the signature.
- *
- * @param associatedData optional associated data bound to the signature (but not in the message)
- * @return the parsed {@link HeaderAndBody} pair (which is fully verified and decrypted)
- * @throws SignatureException if signature verification fails
- * @see SecureMessageBuilder#buildSignCryptedMessage(Key, SigType, Key, EncType, byte[])
- */
- public static HeaderAndBody parseSignCryptedMessage(
- SecureMessage secmsg,
- Key verificationKey,
- SigType sigType,
- Key decryptionKey,
- EncType encType,
- @Nullable byte[] associatedData)
- throws InvalidKeyException, NoSuchAlgorithmException, SignatureException {
- if ((secmsg == null)
- || (verificationKey == null)
- || (sigType == null)
- || (decryptionKey == null)
- || (encType == null)) {
- throw new NullPointerException();
- }
- if (encType == EncType.NONE) {
- throw new SignatureException("Not a signcrypted message");
- }
-
- boolean tagRequired =
- SecureMessageBuilder.taggedPlaintextRequired(verificationKey, sigType, decryptionKey);
- HeaderAndBody headerAndEncryptedBody;
- headerAndEncryptedBody = verifyHeaderAndBody(
- secmsg,
- verificationKey,
- sigType,
- encType,
- associatedData,
- tagRequired /* suppressAssociatedData if it is handled by the tag instead */);
-
- byte[] rawDecryptedBody;
- Header header = headerAndEncryptedBody.getHeader();
- if (!header.hasIv()) {
- throw new SignatureException();
- }
- try {
- rawDecryptedBody = CryptoOps.decrypt(
- decryptionKey, encType, header.getIv().toByteArray(),
- headerAndEncryptedBody.getBody().toByteArray());
- } catch (InvalidAlgorithmParameterException e) {
- throw new SignatureException();
- } catch (IllegalBlockSizeException e) {
- throw new SignatureException();
- } catch (BadPaddingException e) {
- throw new SignatureException();
- }
-
- if (!tagRequired) {
- // No tag expected, so we're all done
- return HeaderAndBody.newBuilder(headerAndEncryptedBody)
- .setBody(ByteString.copyFrom(rawDecryptedBody))
- .build();
- }
-
- // Verify the tag that binds the ciphertext to the header, and remove it
- byte[] headerBytes;
- try {
- headerBytes =
- HeaderAndBodyInternal.parseFrom(secmsg.getHeaderAndBody()).getHeader().toByteArray();
- } catch (InvalidProtocolBufferException e) {
- // This shouldn't happen, but throw it up just in case
- throw new SignatureException(e);
- }
- boolean verifiedBinding = false;
- byte[] expectedTag = CryptoOps.digest(CryptoOps.concat(headerBytes, associatedData));
- if (rawDecryptedBody.length >= CryptoOps.DIGEST_LENGTH) {
- byte[] actualTag = CryptoOps.subarray(rawDecryptedBody, 0, CryptoOps.DIGEST_LENGTH);
- if (CryptoOps.constantTimeArrayEquals(actualTag, expectedTag)) {
- verifiedBinding = true;
- }
- }
- if (!verifiedBinding) {
- throw new SignatureException();
- }
-
- int bodyLen = rawDecryptedBody.length - CryptoOps.DIGEST_LENGTH;
- return HeaderAndBody.newBuilder(headerAndEncryptedBody)
- // Remove the tag and set the plaintext body
- .setBody(ByteString.copyFrom(rawDecryptedBody, CryptoOps.DIGEST_LENGTH, bodyLen))
- .build();
- }
-
- private static HeaderAndBody verifyHeaderAndBody(
- SecureMessage secmsg,
- Key verificationKey,
- SigType sigType,
- EncType encType,
- @Nullable byte[] associatedData,
- boolean suppressAssociatedData /* in case it is in the tag instead */)
- throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
- if (!secmsg.hasHeaderAndBody() || !secmsg.hasSignature()) {
- throw new SignatureException("Signature failed verification");
- }
- byte[] signature = secmsg.getSignature().toByteArray();
- byte[] data = secmsg.getHeaderAndBody().toByteArray();
- byte[] signedData = suppressAssociatedData ? data : CryptoOps.concat(data, associatedData);
-
- // Try not to leak the specific reason for verification failures, due to security concerns.
- boolean verified = CryptoOps.verify(verificationKey, sigType, signature, signedData);
- HeaderAndBody result = null;
- try {
- result = HeaderAndBody.parseFrom(secmsg.getHeaderAndBody());
- // Even if declared required, micro proto doesn't throw an exception if fields are not present
- if (!result.hasHeader() || !result.hasBody()) {
- throw new SignatureException("Signature failed verification");
- }
- verified &= (result.getHeader().getSignatureScheme() == sigType.getSigScheme());
- verified &= (result.getHeader().getEncryptionScheme() == encType.getEncScheme());
- // Check that either a decryption operation is expected, or no DecryptionKeyId is set.
- verified &= (encType != EncType.NONE) || !result.getHeader().hasDecryptionKeyId();
- // If encryption was used, check that either we are not using a public key signature or a
- // VerificationKeyId was set (as is required for public key based signature + encryption).
- verified &= (encType == EncType.NONE) || !sigType.isPublicKeyScheme() ||
- result.getHeader().hasVerificationKeyId();
- int associatedDataLength = associatedData == null ? 0 : associatedData.length;
- verified &= (result.getHeader().getAssociatedDataLength() == associatedDataLength);
- } catch (InvalidProtocolBufferException e) {
- verified = false;
- }
-
- if (verified) {
- return result;
- }
- throw new SignatureException("Signature failed verification");
- }
-}
diff --git a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/D2DConnectionContextTest.java b/src/main/javatest/com/google/security/cryptauth/lib/securegcm/D2DConnectionContextTest.java
deleted file mode 100644
index e671e8c..0000000
--- a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/D2DConnectionContextTest.java
+++ /dev/null
@@ -1,568 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.security.cryptauth.lib.securegcm;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import java.security.SignatureException;
-import java.util.Arrays;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.SecretKeySpec;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-/**
- * Base class for Android compatible tests for {@link D2DConnectionContext} subclasses.
- * Note: We would use a Parameterized test runner to test different versions, but this
- * functionality is not supported by Android tests.
- */
-@RunWith(JUnit4.class)
-public class D2DConnectionContextTest {
- private static final String PING = "ping";
- private static final String PONG = "pong";
-
- // Key is: "initiator_encode_key_for_aes_256"
- private static final SecretKey INITIATOR_ENCODE_KEY = new SecretKeySpec(
- new byte[] {
- (byte) 0x69, (byte) 0x6e, (byte) 0x69, (byte) 0x74, (byte) 0x69, (byte) 0x61, (byte) 0x74,
- (byte) 0x6f, (byte) 0x72, (byte) 0x5f, (byte) 0x65, (byte) 0x6e, (byte) 0x63, (byte) 0x6f,
- (byte) 0x64, (byte) 0x65, (byte) 0x5f, (byte) 0x6b, (byte) 0x65, (byte) 0x79, (byte) 0x5f,
- (byte) 0x66, (byte) 0x6f, (byte) 0x72, (byte) 0x5f, (byte) 0x61, (byte) 0x65, (byte) 0x73,
- (byte) 0x5f, (byte) 0x32, (byte) 0x35, (byte) 0x36
- },
- "AES");
-
- // Key is: "initiator_decode_key_for_aes_256"
- private static final SecretKey INITIATOR_DECODE_KEY = new SecretKeySpec(
- new byte[] {
- (byte) 0x69, (byte) 0x6e, (byte) 0x69, (byte) 0x74, (byte) 0x69, (byte) 0x61, (byte) 0x74,
- (byte) 0x6f, (byte) 0x72, (byte) 0x5f, (byte) 0x64, (byte) 0x65, (byte) 0x63, (byte) 0x6f,
- (byte) 0x64, (byte) 0x65, (byte) 0x5f, (byte) 0x6b, (byte) 0x65, (byte) 0x79, (byte) 0x5f,
- (byte) 0x66, (byte) 0x6f, (byte) 0x72, (byte) 0x5f, (byte) 0x61, (byte) 0x65, (byte) 0x73,
- (byte) 0x5f, (byte) 0x32, (byte) 0x35, (byte) 0x36
- },
- "AES");
-
- private D2DConnectionContext initiatorCtx;
- private D2DConnectionContext responderCtx;
-
- @Before
- public void setUp() throws Exception {
- KeyEncodingTest.installSunEcSecurityProviderIfNecessary();
- }
-
- protected void testPeerToPeerProtocol(int protocolVersion) throws Exception {
-
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- initiatorCtx = createConnectionContext(protocolVersion, true /** isInitiator */);
- responderCtx = createConnectionContext(protocolVersion, false /** isInitiator */);
-
- byte[] pingMessage = initiatorCtx.encodeMessageToPeer(PING);
- // (send message to responder)
-
- // responder
- String messageStr = responderCtx.decodeMessageFromPeerAsString(pingMessage);
- assertEquals(PING, messageStr);
-
- byte[] pongMessage = responderCtx.encodeMessageToPeer(PONG);
- // (send message to initiator)
-
- // initiator
- messageStr = initiatorCtx.decodeMessageFromPeerAsString(pongMessage);
- assertEquals(PONG, messageStr);
-
- // let's make sure there is actually some crypto involved.
- pingMessage = initiatorCtx.encodeMessageToPeer("can you see this?");
- pingMessage[2] = (byte) (pingMessage[2] + 1); // twiddle with the message
- try {
- responderCtx.decodeMessageFromPeerAsString(pingMessage);
- fail("expected exception, but didn't get it");
- } catch (SignatureException expected) {
- assertTrue(expected.getMessage().contains("failed verification"));
- }
-
- // Try and replay the previous encoded message to the initiator (replays should not work).
- try {
- initiatorCtx.decodeMessageFromPeerAsString(pongMessage);
- fail("expected exception, but didn't get it");
- } catch (SignatureException expected) {
- assertTrue(expected.getMessage().contains("sequence"));
- }
-
- assertEquals(protocolVersion, initiatorCtx.getProtocolVersion());
- assertEquals(protocolVersion, responderCtx.getProtocolVersion());
- }
-
- @Test
- public void testPeerToPeerProtocol_V0() throws Exception {
- testPeerToPeerProtocol(D2DConnectionContextV0.PROTOCOL_VERSION);
- }
-
- @Test
- public void testPeerToPeerProtocol_V1() throws Exception {
- testPeerToPeerProtocol(D2DConnectionContextV1.PROTOCOL_VERSION);
- }
-
- protected void testResponderSendsFirst(int protocolVersion) throws Exception {
-
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- initiatorCtx = createConnectionContext(protocolVersion, true /** isInitiator */);
- responderCtx = createConnectionContext(protocolVersion, false /** isInitiator */);
-
- byte[] pongMessage = responderCtx.encodeMessageToPeer(PONG);
- assertEquals(PONG, initiatorCtx.decodeMessageFromPeerAsString(pongMessage));
-
- pongMessage = responderCtx.encodeMessageToPeer(PONG);
- assertEquals(PONG, initiatorCtx.decodeMessageFromPeerAsString(pongMessage));
-
- // for good measure, if the initiator now responds, it should also work:
- byte[] pingMessage = initiatorCtx.encodeMessageToPeer(PING);
- assertEquals(PING, responderCtx.decodeMessageFromPeerAsString(pingMessage));
-
- pingMessage = initiatorCtx.encodeMessageToPeer(PING);
- assertEquals(PING, responderCtx.decodeMessageFromPeerAsString(pingMessage));
-
- pingMessage = initiatorCtx.encodeMessageToPeer(PING);
- assertEquals(PING, responderCtx.decodeMessageFromPeerAsString(pingMessage));
- }
-
- @Test
- public void testResponderSendsFirst_V0() throws Exception {
- testResponderSendsFirst(D2DConnectionContextV0.PROTOCOL_VERSION);
- }
-
- @Test
- public void testResponderSendsFirst_V1() throws Exception {
- testResponderSendsFirst(D2DConnectionContextV1.PROTOCOL_VERSION);
- }
-
- protected void testAssymmetricFlows(int protocolVersion) throws Exception {
-
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- initiatorCtx = createConnectionContext(protocolVersion, true /** isInitiator */);
- responderCtx = createConnectionContext(protocolVersion, false /** isInitiator */);
-
- // Let's test that this still works if one side sends a few messages in a row.
- byte[] pingMessage = initiatorCtx.encodeMessageToPeer(PING);
- assertEquals(PING, responderCtx.decodeMessageFromPeerAsString(pingMessage));
-
- pingMessage = initiatorCtx.encodeMessageToPeer(PING);
- assertEquals(PING, responderCtx.decodeMessageFromPeerAsString(pingMessage));
-
- pingMessage = initiatorCtx.encodeMessageToPeer(PING);
- assertEquals(PING, responderCtx.decodeMessageFromPeerAsString(pingMessage));
-
-
- byte[] pongMessage = responderCtx.encodeMessageToPeer(PONG);
- assertEquals(PONG, initiatorCtx.decodeMessageFromPeerAsString(pongMessage));
-
- pongMessage = responderCtx.encodeMessageToPeer(PONG);
- assertEquals(PONG, initiatorCtx.decodeMessageFromPeerAsString(pongMessage));
- }
-
- @Test
- public void testAssymmetricFlows_V0() throws Exception {
- testAssymmetricFlows(D2DConnectionContextV0.PROTOCOL_VERSION);
- }
-
- @Test
- public void testAssymmetricFlows_V1() throws Exception {
- testAssymmetricFlows(D2DConnectionContextV1.PROTOCOL_VERSION);
- }
-
- public void testErrorWhenResponderResendsMessage(int protocolVersion) throws Exception {
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- initiatorCtx = createConnectionContext(protocolVersion, true /** isInitiator */);
- responderCtx = createConnectionContext(protocolVersion, false /** isInitiator */);
-
- byte[] pongMessage = responderCtx.encodeMessageToPeer(PONG);
- assertEquals(PONG, initiatorCtx.decodeMessageFromPeerAsString(pongMessage));
-
- try {
- // send pongMessage again to the initiator
- initiatorCtx.decodeMessageFromPeerAsString(pongMessage);
- fail("expected exception, but didn't get it");
- } catch (SignatureException expected) {
- assertTrue(expected.getMessage().contains("sequence"));
- }
- }
-
- @Test
- public void testErrorWhenResponderResendsMessage_V0() throws Exception {
- testErrorWhenResponderResendsMessage(D2DConnectionContextV0.PROTOCOL_VERSION);
- }
-
- @Test
- public void testErrorWhenResponderResendsMessage_V1() throws Exception {
- testErrorWhenResponderResendsMessage(D2DConnectionContextV1.PROTOCOL_VERSION);
- }
-
- protected void testErrorWhenResponderEchoesInitiatorMessage(
- int protocolVersion) throws Exception {
- if (KeyEncoding.isLegacyCryptoRequired()) {
- return;
- }
-
- initiatorCtx = createConnectionContext(protocolVersion, true /** isInitiator */);
- responderCtx = createConnectionContext(protocolVersion, false /** isInitiator */);
-
- byte[] pingMessage = initiatorCtx.encodeMessageToPeer(PING);
- assertEquals(PING, responderCtx.decodeMessageFromPeerAsString(pingMessage));
-
- try {
- initiatorCtx.decodeMessageFromPeerAsString(pingMessage);
- fail("expected exception, but didn't get it");
- } catch (SignatureException expected) {
- }
- }
-
- @Test
- public void testErrorWhenResponderEchoesInitiatorMessage_V0() throws Exception {
- testErrorWhenResponderEchoesInitiatorMessage(D2DConnectionContextV0.PROTOCOL_VERSION);
- }
-
- @Test
- public void testErrorWhenResponderEchoesInitiatorMessage_V1() throws Exception {
- testErrorWhenResponderEchoesInitiatorMessage(D2DConnectionContextV1.PROTOCOL_VERSION);
- }
-
- @Test
- public void testErrorUsingV1InitiatorWithV0Responder() throws SignatureException {
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- initiatorCtx = new D2DConnectionContextV1(INITIATOR_ENCODE_KEY, INITIATOR_DECODE_KEY, 1, 1);
- responderCtx = new D2DConnectionContextV0(INITIATOR_DECODE_KEY, 1);
-
- // Decoding the responder's message should succeed, because the decode key and sequence numbers
- // match.
- initiatorCtx.decodeMessageFromPeer(responderCtx.encodeMessageToPeer(PING));
-
- // Responder fails to decodes initiator's encoded message because keys do not match.
- try {
- responderCtx.decodeMessageFromPeer(initiatorCtx.encodeMessageToPeer(PONG));
- fail("Expected verification to fail.");
- } catch (SignatureException e) {
- // Exception expected.
- }
- }
-
- @Test
- public void testErrorWithV0InitiatorV1Responder() throws SignatureException {
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- initiatorCtx = new D2DConnectionContextV0(INITIATOR_ENCODE_KEY, 1);
- responderCtx = new D2DConnectionContextV1(INITIATOR_DECODE_KEY, INITIATOR_ENCODE_KEY, 1, 1);
-
- // Decoding the initiator's message should succeed, because the decode key and sequence numbers
- // match.
- responderCtx.decodeMessageFromPeer(initiatorCtx.encodeMessageToPeer(PING));
-
- // Initiator fails to decodes responder's encoded message because keys do not match.
- try {
- initiatorCtx.decodeMessageFromPeer(responderCtx.encodeMessageToPeer(PONG));
- fail("Expected verification to fail.");
- } catch (SignatureException e) {
- // Exception expected.
- }
- }
-
- protected void testSessionUnique(int protocolVersion) throws Exception {
- // Should be the same (we set them up with the same key and sequence number)
- initiatorCtx = createConnectionContext(protocolVersion, true /** isInitiator */);
- responderCtx = createConnectionContext(protocolVersion, false /** isInitiator */);
- Assert.assertArrayEquals(initiatorCtx.getSessionUnique(), responderCtx.getSessionUnique());
-
- // Change just the key (should not match)
- SecretKey wrongKey = new SecretKeySpec("wrong".getBytes("UTF8"), "AES");
- responderCtx = createConnectionContext(protocolVersion, false, wrongKey, wrongKey, 0, 1);
- assertFalse(Arrays.equals(initiatorCtx.getSessionUnique(), responderCtx.getSessionUnique()));
-
- // Change just the sequence number (should still match)
- responderCtx = createConnectionContext(
- protocolVersion, false, INITIATOR_ENCODE_KEY, INITIATOR_DECODE_KEY, 2, 2);
- Assert.assertArrayEquals(initiatorCtx.getSessionUnique(), responderCtx.getSessionUnique());
- }
-
- @Test
- public void testSessionUnique_V0() throws Exception {
- testSessionUnique(D2DConnectionContextV0.PROTOCOL_VERSION);
- }
-
- @Test
- public void testSessionUnique_V1() throws Exception {
- testSessionUnique(D2DConnectionContextV1.PROTOCOL_VERSION);
- }
-
- @Test
- public void testSessionUniqueValues_V0() throws Exception {
- // The key and the session unique value should match ones in the equivalent test in
- // @link {cs/Nearby/D2DCrypto/Tests/D2DConnectionContextTest.m}
- byte[] key =
- new byte[] {
- (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04, (byte) 0x05, (byte) 0x06, (byte) 0x07,
- (byte) 0x08, (byte) 0x09, (byte) 0x0a, (byte) 0x0b, (byte) 0x0c, (byte) 0x0d, (byte) 0x0e,
- (byte) 0x0f, (byte) 0x10, (byte) 0x11, (byte) 0x12, (byte) 0x13, (byte) 0x14, (byte) 0x15,
- (byte) 0x16, (byte) 0x17, (byte) 0x18, (byte) 0x19, (byte) 0x1a, (byte) 0x1b, (byte) 0x1c,
- (byte) 0x1d, (byte) 0x1e, (byte) 0x1f, (byte) 0x20
- };
- byte[] sessionUnique =
- new byte[] {
- (byte) 0x70, (byte) 0x7a, (byte) 0x17, (byte) 0x27, (byte) 0xa3, (byte) 0x0e, (byte) 0x68,
- (byte) 0x63, (byte) 0x38, (byte) 0xdf, (byte) 0x72, (byte) 0x62, (byte) 0xf4, (byte) 0xb0,
- (byte) 0x41, (byte) 0xac, (byte) 0x75, (byte) 0x8b, (byte) 0xca, (byte) 0x3b, (byte) 0x11,
- (byte) 0xd4, (byte) 0x09, (byte) 0x64, (byte) 0x96, (byte) 0x54, (byte) 0xb4, (byte) 0x9b,
- (byte) 0x43, (byte) 0xe6, (byte) 0x9b, (byte) 0xce
- };
-
- SecretKey secretKey = new SecretKeySpec(key, "AES");
- D2DConnectionContext context = new D2DConnectionContextV0(secretKey, 1);
-
- Assert.assertArrayEquals(context.getSessionUnique(), sessionUnique);
- }
-
- @Test
- public void testSessionUniqueValues_V1_Initiator() throws Exception {
- // The key and the session unique value should match ones in the equivalent test in
- // @link {cs/Nearby/D2DCrypto/Tests/D2DConnectionContextTest.m}
- byte[] sessionUnique =
- new byte[] {
- (byte) 0x91, (byte) 0xc7, (byte) 0xc9, (byte) 0x26, (byte) 0x2c, (byte) 0x17, (byte) 0x8a,
- (byte) 0xa0, (byte) 0x36, (byte) 0x9f, (byte) 0xf2, (byte) 0x05, (byte) 0x20, (byte) 0x98,
- (byte) 0x38, (byte) 0x53, (byte) 0xa5, (byte) 0x46, (byte) 0xab, (byte) 0x3a, (byte) 0x21,
- (byte) 0x3b, (byte) 0x76, (byte) 0x58, (byte) 0x59, (byte) 0x4e, (byte) 0xe7, (byte) 0xe3,
- (byte) 0xc1, (byte) 0x69, (byte) 0x87, (byte) 0xfa
- };
-
- D2DConnectionContext initiatorContext = new D2DConnectionContextV1(
- INITIATOR_ENCODE_KEY, INITIATOR_DECODE_KEY, 0, 1);
- D2DConnectionContext responderContext = new D2DConnectionContextV1(
- INITIATOR_DECODE_KEY, INITIATOR_ENCODE_KEY, 1, 0);
-
- // Both the initiator and responder must be the same.
- Assert.assertArrayEquals(initiatorContext.getSessionUnique(), sessionUnique);
- Assert.assertArrayEquals(responderContext.getSessionUnique(), sessionUnique);
- }
-
- @Test
- public void testSaveSessionV0() throws Exception {
- D2DConnectionContext initiatorCtx = new D2DConnectionContextV0(INITIATOR_ENCODE_KEY, 1);
- D2DConnectionContext responderCtx = new D2DConnectionContextV0(INITIATOR_ENCODE_KEY, 1);
-
- // Save the state
- byte[] initiatorSavedSessionState = initiatorCtx.saveSession();
- byte[] responderSavedSessionState = responderCtx.saveSession();
-
- // Try to rebuild the context
- initiatorCtx = D2DConnectionContext.fromSavedSession(initiatorSavedSessionState);
- responderCtx = D2DConnectionContext.fromSavedSession(responderSavedSessionState);
-
- // Sanity check
- assertEquals(1, initiatorCtx.getSequenceNumberForDecoding());
- assertEquals(1, responderCtx.getSequenceNumberForDecoding());
- Assert.assertArrayEquals(initiatorCtx.getSessionUnique(), responderCtx.getSessionUnique());
-
- // Make sure they can still talk to one another
- assertEquals(PING,
- responderCtx.decodeMessageFromPeerAsString(initiatorCtx.encodeMessageToPeer(PING)));
- assertEquals(PONG,
- initiatorCtx.decodeMessageFromPeerAsString(responderCtx.encodeMessageToPeer(PONG)));
- }
-
- @Test
- public void testSaveSessionV0_negativeSeqNumber() throws Exception {
- D2DConnectionContext initiatorCtx = new D2DConnectionContextV0(INITIATOR_ENCODE_KEY, -5);
-
- // Save the state
- byte[] initiatorSavedSessionState = initiatorCtx.saveSession();
-
- // Try to rebuild the context
- initiatorCtx = D2DConnectionContext.fromSavedSession(initiatorSavedSessionState);
-
- // Sanity check
- assertEquals(-5, initiatorCtx.getSequenceNumberForDecoding());
- }
-
- @Test
- public void testSaveSessionV0_shortKey() throws Exception {
- D2DConnectionContext initiatorCtx = new D2DConnectionContextV0(INITIATOR_ENCODE_KEY, -5);
-
- // Save the state
- byte[] initiatorSavedSessionState = initiatorCtx.saveSession();
-
- // Try to rebuild the context
- try {
- D2DConnectionContext.fromSavedSession(Arrays.copyOf(initiatorSavedSessionState,
- initiatorSavedSessionState.length - 1));
- fail("Expected failure as key is too short");
- } catch (IllegalArgumentException e) {
- // expected
- }
- }
-
- @Test
- public void testSaveSession_unknownProtocolVersion() throws Exception {
- D2DConnectionContext initiatorCtx = new D2DConnectionContextV0(INITIATOR_ENCODE_KEY, -5);
-
- // Save the state
- byte[] initiatorSavedSessionState = initiatorCtx.saveSession();
-
- // Mess with the protocol version
- initiatorSavedSessionState[0] = (byte) 0xff;
-
- // Try to rebuild the context
- try {
- D2DConnectionContext.fromSavedSession(initiatorSavedSessionState);
- fail("Expected failure as 0xff is not a valid protocol version");
- } catch (IllegalArgumentException e) {
- // expected
- }
-
- // Mess with the protocol version in the other direction
- initiatorSavedSessionState[0] = 2;
-
- // Try to rebuild the context
- try {
- D2DConnectionContext.fromSavedSession(initiatorSavedSessionState);
- fail("Expected failure as 2 is not a valid protocol version");
- } catch (IllegalArgumentException e) {
- // expected
- }
- }
-
- @Test
- public void testSaveSessionV1() throws Exception {
- D2DConnectionContext initiatorCtx = new D2DConnectionContextV1(INITIATOR_ENCODE_KEY,
- INITIATOR_DECODE_KEY, 0, 1);
- D2DConnectionContext responderCtx = new D2DConnectionContextV1(INITIATOR_DECODE_KEY,
- INITIATOR_ENCODE_KEY, 1, 0);
-
- // Save the state
- byte[] initiatorSavedSessionState = initiatorCtx.saveSession();
- byte[] responderSavedSessionState = responderCtx.saveSession();
-
- // Try to rebuild the context
- initiatorCtx = D2DConnectionContext.fromSavedSession(initiatorSavedSessionState);
- responderCtx = D2DConnectionContext.fromSavedSession(responderSavedSessionState);
-
- // Sanity check
- assertEquals(1, initiatorCtx.getSequenceNumberForDecoding());
- assertEquals(0, initiatorCtx.getSequenceNumberForEncoding());
- assertEquals(0, responderCtx.getSequenceNumberForDecoding());
- assertEquals(1, responderCtx.getSequenceNumberForEncoding());
- Assert.assertArrayEquals(initiatorCtx.getSessionUnique(), responderCtx.getSessionUnique());
-
- // Make sure they can still talk to one another
- assertEquals(PING,
- responderCtx.decodeMessageFromPeerAsString(initiatorCtx.encodeMessageToPeer(PING)));
- assertEquals(PONG,
- initiatorCtx.decodeMessageFromPeerAsString(responderCtx.encodeMessageToPeer(PONG)));
- }
-
- @Test
- public void testSaveSessionV1_negativeSeqNumbers() throws Exception {
- D2DConnectionContext initiatorCtx = new D2DConnectionContextV1(INITIATOR_ENCODE_KEY,
- INITIATOR_DECODE_KEY, -8, -10);
-
- // Save the state
- byte[] initiatorSavedSessionState = initiatorCtx.saveSession();
-
- // Try to rebuild the context
- initiatorCtx = D2DConnectionContext.fromSavedSession(initiatorSavedSessionState);
-
- // Sanity check
- assertEquals(-10, initiatorCtx.getSequenceNumberForDecoding());
- assertEquals(-8, initiatorCtx.getSequenceNumberForEncoding());
- }
-
- @Test
- public void testSaveSessionV1_tooShort() throws Exception {
- D2DConnectionContext initiatorCtx = new D2DConnectionContextV1(INITIATOR_ENCODE_KEY,
- INITIATOR_DECODE_KEY, -8, -10);
-
- // Save the state
- byte[] initiatorSavedSessionState = initiatorCtx.saveSession();
-
- // Try to rebuild the context
- try {
- D2DConnectionContext.fromSavedSession(
- Arrays.copyOf(initiatorSavedSessionState, initiatorSavedSessionState.length - 1));
- fail("Expected error as saved session is too short");
- } catch (IllegalArgumentException e) {
- // expected
- }
-
- // Sanity check
- assertEquals(-10, initiatorCtx.getSequenceNumberForDecoding());
- assertEquals(-8, initiatorCtx.getSequenceNumberForEncoding());
- }
-
- D2DConnectionContext createConnectionContext(int protocolVersion, boolean isInitiator) {
- return createConnectionContext(
- protocolVersion, isInitiator, INITIATOR_ENCODE_KEY, INITIATOR_DECODE_KEY, 0, 1);
- }
-
- D2DConnectionContext createConnectionContext(
- int protocolVersion, boolean isInitiator,
- SecretKey initiatorEncodeKey, SecretKey initiatorDecodeKey,
- int initiatorSequenceNumber, int responderSequenceNumber) {
- if (protocolVersion == D2DConnectionContextV0.PROTOCOL_VERSION) {
- return new D2DConnectionContextV0(initiatorEncodeKey, responderSequenceNumber);
- } else if (protocolVersion == D2DConnectionContextV1.PROTOCOL_VERSION) {
- return isInitiator
- ? new D2DConnectionContextV1(
- initiatorEncodeKey, initiatorDecodeKey,
- initiatorSequenceNumber, responderSequenceNumber)
- : new D2DConnectionContextV1(
- initiatorDecodeKey, initiatorEncodeKey,
- responderSequenceNumber, initiatorSequenceNumber);
- } else {
- throw new IllegalArgumentException("Unknown version: " + protocolVersion);
- }
- }
-}
diff --git a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/D2DDiffieHellmanKeyExchangeHandshakeTest.java b/src/main/javatest/com/google/security/cryptauth/lib/securegcm/D2DDiffieHellmanKeyExchangeHandshakeTest.java
deleted file mode 100644
index 4de794a..0000000
--- a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/D2DDiffieHellmanKeyExchangeHandshakeTest.java
+++ /dev/null
@@ -1,432 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.security.cryptauth.lib.securegcm;
-
-import com.google.security.cryptauth.lib.securegcm.DeviceToDeviceMessagesProto.InitiatorHello;
-import com.google.security.cryptauth.lib.securegcm.DeviceToDeviceMessagesProto.ResponderHello;
-import com.google.security.cryptauth.lib.securegcm.TransportCryptoOps.Payload;
-import com.google.security.cryptauth.lib.securegcm.TransportCryptoOps.PayloadType;
-import com.google.security.cryptauth.lib.securemessage.PublicKeyProtoUtil;
-import java.nio.charset.Charset;
-import java.security.KeyPair;
-import java.security.PublicKey;
-import java.security.SignatureException;
-import javax.crypto.SecretKey;
-import junit.framework.TestCase;
-import org.junit.Assert;
-
-/**
- * Android compatible tests for the {@link D2DDiffieHellmanKeyExchangeHandshake} class.
- */
-public class D2DDiffieHellmanKeyExchangeHandshakeTest extends TestCase {
-
- private static final byte[] RESPONDER_HELLO_MESSAGE =
- "first payload".getBytes(Charset.forName("UTF-8"));
-
- private static final String PING = "ping";
-
- @Override
- protected void setUp() throws Exception {
- KeyEncodingTest.installSunEcSecurityProviderIfNecessary();
- super.setUp();
- }
-
- public void testHandshakeWithPayload() throws Exception {
-
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- // initiator:
- D2DHandshakeContext initiatorHandshakeContext =
- D2DDiffieHellmanKeyExchangeHandshake.forInitiator();
- assertFalse(initiatorHandshakeContext.canSendPayloadInHandshakeMessage());
- assertFalse(initiatorHandshakeContext.isHandshakeComplete());
- byte[] initiatorHello = initiatorHandshakeContext.getNextHandshakeMessage();
- assertFalse(initiatorHandshakeContext.isHandshakeComplete());
- // (send initiatorHello to responder)
-
- // responder:
- D2DHandshakeContext responderHandshakeContext =
- D2DDiffieHellmanKeyExchangeHandshake.forResponder();
- byte[] payload = responderHandshakeContext.parseHandshakeMessage(initiatorHello);
- assertEquals(0, payload.length);
- assertTrue(responderHandshakeContext.canSendPayloadInHandshakeMessage());
- assertFalse(responderHandshakeContext.isHandshakeComplete());
- byte[] responderHelloAndPayload = responderHandshakeContext.getNextHandshakeMessage(
- RESPONDER_HELLO_MESSAGE);
- assertTrue(responderHandshakeContext.isHandshakeComplete());
- D2DConnectionContext responderCtx = responderHandshakeContext.toConnectionContext();
- // (send responderHelloAndPayload to initiator)
-
- // initiator
- byte[] messageFromPayload =
- initiatorHandshakeContext.parseHandshakeMessage(responderHelloAndPayload);
- Assert.assertArrayEquals(RESPONDER_HELLO_MESSAGE, messageFromPayload);
- assertTrue(initiatorHandshakeContext.isHandshakeComplete());
- D2DConnectionContextV1 initiatorCtx =
- (D2DConnectionContextV1) initiatorHandshakeContext.toConnectionContext();
-
- // Test that that initiator and responder contexts are initialized correctly.
- checkInitializedConnectionContexts(initiatorCtx, responderCtx);
- }
-
- public void testHandshakeWithoutPayload() throws Exception {
-
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- // initiator:
- D2DHandshakeContext initiatorHandshakeContext =
- D2DDiffieHellmanKeyExchangeHandshake.forInitiator();
- byte[] initiatorHello = initiatorHandshakeContext.getNextHandshakeMessage();
- // (send initiatorHello to responder)
-
- // responder:
- D2DHandshakeContext responderHandshakeContext =
- D2DDiffieHellmanKeyExchangeHandshake.forResponder();
- responderHandshakeContext.parseHandshakeMessage(initiatorHello);
- byte[] responderHelloAndPayload = responderHandshakeContext.getNextHandshakeMessage();
- assertTrue(responderHandshakeContext.isHandshakeComplete());
- D2DConnectionContext responderCtx = responderHandshakeContext.toConnectionContext();
- // (send responderHelloAndPayload to initiator)
-
- // initiator
- byte[] messageFromPayload =
- initiatorHandshakeContext.parseHandshakeMessage(responderHelloAndPayload);
- assertEquals(0, messageFromPayload.length);
- assertTrue(initiatorHandshakeContext.isHandshakeComplete());
- D2DConnectionContext initiatorCtx = initiatorHandshakeContext.toConnectionContext();
-
- // Test that that initiator and responder contexts are initialized correctly.
- checkInitializedConnectionContexts(initiatorCtx, responderCtx);
- }
-
- public void testErrorWhenInitiatorOrResponderSendTwice() throws Exception {
-
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- // initiator:
- D2DHandshakeContext initiatorHandshakeContext =
- D2DDiffieHellmanKeyExchangeHandshake.forInitiator();
- byte[] initiatorHello = initiatorHandshakeContext.getNextHandshakeMessage();
- try {
- initiatorHandshakeContext.getNextHandshakeMessage();
- fail("Expected error as initiator has no more initiator messages to send");
- } catch (HandshakeException expected) {
- assertTrue(expected.getMessage().contains("Cannot get next message"));
- }
- // (send initiatorHello to responder)
-
- // responder:
- D2DHandshakeContext responderHandshakeContext =
- D2DDiffieHellmanKeyExchangeHandshake.forResponder();
- responderHandshakeContext.parseHandshakeMessage(initiatorHello);
- responderHandshakeContext.getNextHandshakeMessage();
- try {
- responderHandshakeContext.getNextHandshakeMessage();
- fail("Expected error as initiator has no more responder messages to send");
- } catch (HandshakeException expected) {
- assertTrue(expected.getMessage().contains("Cannot get"));
- }
- }
-
- public void testInitiatorOrResponderFailOnEmptyMessage() throws Exception {
-
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- D2DHandshakeContext handshakeContext =
- D2DDiffieHellmanKeyExchangeHandshake.forInitiator();
- try {
- handshakeContext.parseHandshakeMessage(null);
- fail("Expected to crash on null message");
- } catch (HandshakeException expected) {
- assertTrue(expected.getMessage().contains("short"));
- }
- try {
- handshakeContext.parseHandshakeMessage(new byte[0]);
- fail("Expected to crash on empty message");
- } catch (HandshakeException expected) {
- assertTrue(expected.getMessage().contains("short"));
- }
- }
-
- public void testPrematureConversionToConnection() throws Exception {
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- // initiator:
- D2DHandshakeContext initiatorHandshakeContext =
- D2DDiffieHellmanKeyExchangeHandshake.forInitiator();
- try {
- initiatorHandshakeContext.toConnectionContext();
- fail("Expected to crash: initiator hasn't done anything to deserve full connection");
- } catch (HandshakeException expected) {
- assertTrue(expected.getMessage().contains("not complete"));
- }
-
- byte[] initiatorHello = initiatorHandshakeContext.getNextHandshakeMessage();
- try {
- initiatorHandshakeContext.toConnectionContext();
- fail("Expected to crash: initiator hasn't yet received responder's key");
- } catch (HandshakeException expected) {
- assertTrue(expected.getMessage().contains("not complete"));
- }
- // (send initiatorHello to responder)
-
- // responder:
- D2DHandshakeContext responderHandshakeContext =
- D2DDiffieHellmanKeyExchangeHandshake.forResponder();
- responderHandshakeContext.parseHandshakeMessage(initiatorHello);
- try {
- initiatorHandshakeContext.toConnectionContext();
- fail("Expected to crash: responder hasn't yet send their key");
- } catch (HandshakeException expected) {
- assertTrue(expected.getMessage().contains("not complete"));
- }
- }
-
- public void testCannotReuseHandshakeContext() throws Exception {
-
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- // initiator:
- D2DHandshakeContext initiatorHandshakeContext =
- D2DDiffieHellmanKeyExchangeHandshake.forInitiator();
- byte[] initiatorHello = initiatorHandshakeContext.getNextHandshakeMessage();
- // (send initiatorHello to responder)
-
- // responder:
- D2DHandshakeContext responderHandshakeContext =
- D2DDiffieHellmanKeyExchangeHandshake.forResponder();
- responderHandshakeContext.parseHandshakeMessage(initiatorHello);
- byte[] responderHelloAndPayload = responderHandshakeContext.getNextHandshakeMessage();
- D2DConnectionContext responderCtx = responderHandshakeContext.toConnectionContext();
- // (send responderHelloAndPayload to initiator)
-
- // initiator
- initiatorHandshakeContext.parseHandshakeMessage(responderHelloAndPayload);
- D2DConnectionContext initiatorCtx = initiatorHandshakeContext.toConnectionContext();
-
- // Test that that initiator and responder contexts are initialized correctly.
- checkInitializedConnectionContexts(initiatorCtx, responderCtx);
-
- // Try to get another full context
- try {
- initiatorHandshakeContext.toConnectionContext();
- fail("Expected crash: initiator context has already been used");
- } catch (HandshakeException expected) {
- assertTrue(expected.getMessage().contains("used"));
- }
- try {
- responderHandshakeContext.toConnectionContext();
- fail("Expected crash: responder context has already been used");
- } catch (HandshakeException expected) {
- assertTrue(expected.getMessage().contains("used"));
- }
- }
-
- public void testErrorWhenInitiatorEchosResponderHello() throws Exception {
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- // Initiator echoing back responder's first packet:
- D2DDiffieHellmanKeyExchangeHandshake partialInitiatorContext =
- D2DDiffieHellmanKeyExchangeHandshake.forInitiator();
- byte[] initiatorHello = partialInitiatorContext.getNextHandshakeMessage();
-
- D2DDiffieHellmanKeyExchangeHandshake partialResponderCtx =
- D2DDiffieHellmanKeyExchangeHandshake.forResponder();
- partialResponderCtx.parseHandshakeMessage(initiatorHello);
- byte[] responderHelloAndPayload =
- partialResponderCtx.getNextHandshakeMessage(RESPONDER_HELLO_MESSAGE);
- D2DConnectionContext responderCtx = partialResponderCtx.toConnectionContext();
-
- try {
- // initiator sends responderHelloAndPayload to responder
- responderCtx.decodeMessageFromPeerAsString(responderHelloAndPayload);
- fail("expected exception, but didn't get it");
- } catch (SignatureException expected) {
- assertTrue(expected.getMessage().contains("Signature failed verification"));
- }
- }
-
- public void testErrorWhenInitiatorResendsMessage() throws Exception {
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- // Initiator repeating the same packet twice
- D2DDiffieHellmanKeyExchangeHandshake partialInitiatorContext =
- D2DDiffieHellmanKeyExchangeHandshake.forInitiator();
- byte[] initiatorHello = partialInitiatorContext.getNextHandshakeMessage();
-
- D2DDiffieHellmanKeyExchangeHandshake partialResponderCtx =
- D2DDiffieHellmanKeyExchangeHandshake.forResponder();
- partialResponderCtx.parseHandshakeMessage(initiatorHello);
- byte[] responderHelloAndPayload =
- partialResponderCtx.getNextHandshakeMessage(RESPONDER_HELLO_MESSAGE);
- D2DConnectionContext responderCtx = partialResponderCtx.toConnectionContext();
-
- partialInitiatorContext.parseHandshakeMessage(responderHelloAndPayload);
- D2DConnectionContext initiatorCtx = partialInitiatorContext.toConnectionContext();
-
- byte[] pingMessage = initiatorCtx.encodeMessageToPeer(PING);
- assertEquals(PING, responderCtx.decodeMessageFromPeerAsString(pingMessage));
-
- try {
- // send pingMessage to responder again
- responderCtx.decodeMessageFromPeerAsString(pingMessage);
- fail("expected exception, but didn't get it");
- } catch (SignatureException expected) {
- assertTrue(expected.getMessage().contains("sequence"));
- }
- }
-
- public void testErrorWhenResponderResendsFirstMessage() throws Exception {
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- D2DDiffieHellmanKeyExchangeHandshake partialInitiatorContext =
- D2DDiffieHellmanKeyExchangeHandshake.forInitiator();
- byte[] initiatorHello = partialInitiatorContext.getNextHandshakeMessage();
-
- D2DDiffieHellmanKeyExchangeHandshake partialResponderCtx =
- D2DDiffieHellmanKeyExchangeHandshake.forResponder();
- partialResponderCtx.parseHandshakeMessage(initiatorHello);
- byte[] responderHelloAndPayload =
- partialResponderCtx.getNextHandshakeMessage(RESPONDER_HELLO_MESSAGE);
-
- partialInitiatorContext.parseHandshakeMessage(responderHelloAndPayload);
- D2DConnectionContext initiatorCtx = partialInitiatorContext.toConnectionContext();
-
- try {
- // Send the responderHelloAndPayload again. This time, the initiator will
- // process it as a normal message.
- initiatorCtx.decodeMessageFromPeerAsString(responderHelloAndPayload);
- fail("expected exception, but didn't get it");
- } catch (SignatureException expected) {
- assertTrue(expected.getMessage().contains("wrong message type"));
- }
- }
-
- public void testHandshakeWithInitiatorV1AndResponderV0() throws Exception {
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- // Initialize initiator side.
- D2DHandshakeContext initiatorHandshakeContext =
- D2DDiffieHellmanKeyExchangeHandshake.forInitiator();
- byte[] initiatorHello = initiatorHandshakeContext.getNextHandshakeMessage();
-
- // Set up keys used by the responder.
- PublicKey initiatorPublicKey = PublicKeyProtoUtil.parsePublicKey(
- InitiatorHello.parseFrom(initiatorHello).getPublicDhKey());
- KeyPair responderKeyPair = PublicKeyProtoUtil.generateEcP256KeyPair();
- SecretKey sharedKey =
- EnrollmentCryptoOps.doKeyAgreement(responderKeyPair.getPrivate(), initiatorPublicKey);
-
- // Construct a responder hello message without the version field, whose payload is encrypted
- // with the shared key.
- byte[] responderHello = D2DCryptoOps.signcryptPayload(
- new Payload(
- PayloadType.DEVICE_TO_DEVICE_RESPONDER_HELLO_PAYLOAD,
- D2DConnectionContext.createDeviceToDeviceMessage(new byte[] {}, 1).toByteArray()),
- sharedKey,
- ResponderHello.newBuilder()
- .setPublicDhKey(
- PublicKeyProtoUtil.encodePublicKey(responderKeyPair.getPublic()))
- .build().toByteArray());
-
- // Handle V0 responder hello message.
- initiatorHandshakeContext.parseHandshakeMessage(responderHello);
- D2DConnectionContext initiatorCtx = initiatorHandshakeContext.toConnectionContext();
-
- assertEquals(D2DConnectionContextV0.PROTOCOL_VERSION, initiatorCtx.getProtocolVersion());
- assertEquals(1, initiatorCtx.getSequenceNumberForEncoding());
- assertEquals(1, initiatorCtx.getSequenceNumberForDecoding());
- }
-
- public void testHandshakeWithInitiatorV0AndResponderV1() throws Exception {
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- // Construct an initiator hello message without the version field.
- byte[] initiatorHello = InitiatorHello.newBuilder()
- .setPublicDhKey(PublicKeyProtoUtil.encodePublicKey(
- PublicKeyProtoUtil.generateEcP256KeyPair().getPublic()))
- .build()
- .toByteArray();
-
- // Handle V0 initiator hello message.
- D2DHandshakeContext responderHandshakeContext =
- D2DDiffieHellmanKeyExchangeHandshake.forResponder();
- responderHandshakeContext.parseHandshakeMessage(initiatorHello);
- responderHandshakeContext.getNextHandshakeMessage();
- D2DConnectionContext responderCtx = responderHandshakeContext.toConnectionContext();
-
- assertEquals(D2DConnectionContextV0.PROTOCOL_VERSION, responderCtx.getProtocolVersion());
- assertEquals(1, responderCtx.getSequenceNumberForEncoding());
- assertEquals(1, responderCtx.getSequenceNumberForDecoding());
- }
-
- private void checkInitializedConnectionContexts(
- D2DConnectionContext initiatorCtx, D2DConnectionContext responderCtx) {
- assertNotNull(initiatorCtx);
- assertNotNull(responderCtx);
- assertEquals(D2DConnectionContextV1.PROTOCOL_VERSION, initiatorCtx.getProtocolVersion());
- assertEquals(D2DConnectionContextV1.PROTOCOL_VERSION, responderCtx.getProtocolVersion());
- assertEquals(initiatorCtx.getEncodeKey(), responderCtx.getDecodeKey());
- assertEquals(initiatorCtx.getDecodeKey(), responderCtx.getEncodeKey());
- assertEquals(0, initiatorCtx.getSequenceNumberForEncoding());
- assertEquals(1, initiatorCtx.getSequenceNumberForDecoding());
- assertEquals(1, responderCtx.getSequenceNumberForEncoding());
- assertEquals(0, responderCtx.getSequenceNumberForDecoding());
- }
-}
diff --git a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/Ed25519Test.java b/src/main/javatest/com/google/security/cryptauth/lib/securegcm/Ed25519Test.java
deleted file mode 100644
index 6ae95d8..0000000
--- a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/Ed25519Test.java
+++ /dev/null
@@ -1,195 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.security.cryptauth.lib.securegcm;
-
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertThat;
-
-import com.google.security.cryptauth.lib.securegcm.Ed25519.Ed25519Exception;
-import java.math.BigInteger;
-import junit.framework.TestCase;
-
-/**
- * Android compatible tests for the {@link Ed25519} class.
- */
-public class Ed25519Test extends TestCase {
-
- // Points on the curve
- private static final int HEX_RADIX = 16;
- private static final BigInteger[] KM = new BigInteger[] {
- new BigInteger("1981FB43F103290ECF9772022DB8B19BFAF389057ED91E8486EB368763435925", HEX_RADIX),
- new BigInteger("A714C34F3B588AAC92FD2587884A20964FD351A1F147D5C4BBF5C2F37A77C36", HEX_RADIX)};
- private static final BigInteger[] KN = new BigInteger[] {
- new BigInteger("201A184F47D9A7973891D148E3D1C864D8084547131C2C1CEFB7EEBD26C63567", HEX_RADIX),
- new BigInteger("6DA2D3B18EC4F9AA3B08E39C997CD8BF6E9948FFD4FEFFECAF8DD0B3D648B7E8", HEX_RADIX)};
-
- // Curve prime P
- private static final BigInteger P =
- new BigInteger("7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED", HEX_RADIX);
-
- // Test vectors obtain by multiplying KM by k by manually using the official implementation
- // see: http://ed25519.cr.yp.to/python/ed25519.py
- // k = 2
- private static final BigInteger[] KM_2 = new BigInteger[] {
- new BigInteger("718079972e63c2d62caf0ee93ec6f00337ceaff4e283181c04c4082b1d5e1ecf", HEX_RADIX),
- new BigInteger("143d18d393a8058c8614335bf36bf59364cc7c451db74726b322ce9d0b826d51", HEX_RADIX)
- };
- // k = 3
- private static final BigInteger[] KM_3 = new BigInteger[] {
- new BigInteger("39DA3C92EFC0577586B4D58F4A5C0BF65A6CC8F6BF358F38D70B2E6C28A31E8E", HEX_RADIX),
- new BigInteger("6D194F054B3FC2BE217F6A360BBEC747D2937FCEBD74B67FC3B20ED638ADD670", HEX_RADIX)
- };
- // k = 317698
- private static final BigInteger[] KM_317698 = new BigInteger[] {
- new BigInteger("7945D0ADEB568B16495476E81ADF281F4515439AE835914FBF6CEEAFEB9CD7E8", HEX_RADIX),
- new BigInteger("3631503DCDEBC0BF9BB1FFC3984A8CB52A34FFC2E77E9C19FD896DC6EE64A530", HEX_RADIX)
- };
- // k = P
- private static final BigInteger[] KM_HUGE = new BigInteger[] {
- new BigInteger("530162B05F440E00E219DFD3188524821C860C41FD87B9AC6AF2A283FDD585A1", HEX_RADIX),
- new BigInteger("48385A7D2BB858F3DB7F72E7CDFE218B9CA84DDA8BD64C3775AA43551D974F60", HEX_RADIX)
- };
- // k = P + 10000
- private static final BigInteger[] KM_XRAHUGE = new BigInteger[] {
- new BigInteger("16377E9F5EE2C0F4C70E17AC298EF670700A7CB186EEB0DA10CDD59635000AF8", HEX_RADIX),
- new BigInteger("5BD7921EEE662ACBAC3A96D8B6039D2356F154859FAF41FD2F0D99DF06CD2EAE", HEX_RADIX)
- };
-
- // Helpful constants
- private static final BigInteger ONE = BigInteger.ONE;
- private static final BigInteger ZERO = BigInteger.ZERO;
-
- // Identity element of the group (the zero) in affine and extended representations
- private static final BigInteger[] ID = new BigInteger[] {ZERO, ONE};
- private static final BigInteger[] ID_EX = new BigInteger[] {ZERO, ONE, ONE, ZERO};
-
- public void testValidPoints() throws Exception {
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- // We've got a couple of valid points
- Ed25519.validateAffinePoint(KM);
- Ed25519.validateAffinePoint(KN);
-
- // And a bunch of invalid ones
- try {
- Ed25519.validateAffinePoint(new BigInteger[] {ZERO, ONE});
- fail("Validate point not catching zero x coordinates");
- } catch (Ed25519Exception e) {
- assertThat(e.getMessage(), containsString("positive"));
- }
-
- try {
- Ed25519.validateAffinePoint(new BigInteger[] {ONE, ZERO});
- fail("Validate point not catching zero y coordinates");
- } catch (Ed25519Exception e) {
- assertThat(e.getMessage(), containsString("positive"));
- }
-
- try {
- Ed25519.validateAffinePoint(new BigInteger[] {new BigInteger("-1"), ONE});
- fail("Validate point not catching negative x coordinates");
- } catch (Ed25519Exception e) {
- assertThat(e.getMessage(), containsString("positive"));
- }
-
- try {
- Ed25519.validateAffinePoint(new BigInteger[] {ONE, new BigInteger("-1")});
- fail("Validate point not catching negative y coordinates");
- } catch (Ed25519Exception e) {
- assertThat(e.getMessage(), containsString("positive"));
- }
-
- try {
- Ed25519.validateAffinePoint(new BigInteger[] {ONE, ONE});
- fail("Validate point not catching points that are not on curve");
- } catch (Ed25519Exception e) {
- assertThat(e.getMessage(), containsString("expected curve"));
- }
- }
-
- public void testAffineExtendedConversion() throws Exception {
- BigInteger[] km1 = Ed25519.toAffine(Ed25519.toExtended(KM));
- BigInteger[] kn1 = Ed25519.toAffine(Ed25519.toExtended(KN));
-
- assertArrayEquals(KM, km1);
- assertArrayEquals(KN, kn1);
-
- assertArrayEquals(ID, Ed25519.toAffine(ID_EX));
- assertArrayEquals(ID_EX, Ed25519.toExtended(ID));
- }
-
- public void testRepresentationCheck() throws Exception {
- Ed25519.checkPointIsInAffineRepresentation(KM);
- Ed25519.checkPointIsInExtendedRepresentation(ID_EX);
-
- try {
- Ed25519.checkPointIsInExtendedRepresentation(KM);
- fail("Point is not really in extended representation, expected failure");
- } catch (Ed25519Exception e) {
- assertThat(e.getMessage(), containsString("not in extended"));
- }
-
- try {
- Ed25519.checkPointIsInAffineRepresentation(Ed25519.toExtended(KM));
- fail("Point is not really in affine representation, expected failure");
- } catch (Ed25519Exception e) {
- assertThat(e.getMessage(), containsString("not in affine"));
- }
- }
-
- public void testAddSubtractExtendedPoints() throws Exception {
- // Adding/subtracting identity to/from itself should yield the identity point
- assertArrayEquals(ID, Ed25519.addAffinePoints(ID, ID));
- assertArrayEquals(ID, Ed25519.subtractAffinePoints(ID, ID));
-
- // In fact adding/subtracting the identity point to/from any point should yield that point
- assertArrayEquals(KM, Ed25519.addAffinePoints(KM, ID));
- assertArrayEquals(KM, Ed25519.subtractAffinePoints(KM, ID));
-
- // Subtracting a point from itself should yield the identity element
- assertArrayEquals(ID, Ed25519.subtractAffinePoints(KM, KM));
- assertArrayEquals(ID, Ed25519.subtractAffinePoints(KN, KN));
-
- // Adding and subtracting should yield the same point
- assertArrayEquals(KM, Ed25519.subtractAffinePoints(Ed25519.addAffinePoints(KM, KN), KN));
- assertArrayEquals(KN, Ed25519.subtractAffinePoints(Ed25519.addAffinePoints(KN, KM), KM));
- }
-
- public void testScalarMultiplyExtendedPoints() throws Exception {
- // A point times one is the point itself
- assertArrayEquals(KM, Ed25519.scalarMultiplyAffinePoint(KM, ONE));
- assertArrayEquals(KN, Ed25519.scalarMultiplyAffinePoint(KN, ONE));
-
- // A point times zero is the identity point
- assertArrayEquals(ID, Ed25519.scalarMultiplyAffinePoint(KM, ZERO));
- assertArrayEquals(ID, Ed25519.scalarMultiplyAffinePoint(KN, ZERO));
-
- // The identity times a scalar is the identity
- assertArrayEquals(ID, Ed25519.scalarMultiplyAffinePoint(ID, BigInteger.valueOf(317698)));
-
- // Use test vectors
- assertArrayEquals(KM_2, Ed25519.scalarMultiplyAffinePoint(KM, BigInteger.valueOf(2)));
- assertArrayEquals(KM_3, Ed25519.scalarMultiplyAffinePoint(KM, BigInteger.valueOf(3)));
- assertArrayEquals(KM_317698, Ed25519.scalarMultiplyAffinePoint(KM, BigInteger.valueOf(317698)));
- assertArrayEquals(KM_HUGE, Ed25519.scalarMultiplyAffinePoint(KM, P));
- assertArrayEquals(KM_XRAHUGE,
- Ed25519.scalarMultiplyAffinePoint(KM, P.add(BigInteger.valueOf(10000))));
- }
-}
diff --git a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/EnrollmentCryptoOpsTest.java b/src/main/javatest/com/google/security/cryptauth/lib/securegcm/EnrollmentCryptoOpsTest.java
deleted file mode 100644
index 4437045..0000000
--- a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/EnrollmentCryptoOpsTest.java
+++ /dev/null
@@ -1,134 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.security.cryptauth.lib.securegcm;
-
-import com.google.protobuf.ByteString;
-import com.google.security.cryptauth.lib.securegcm.SecureGcmProto.GcmDeviceInfo;
-import com.google.security.cryptauth.lib.securemessage.PublicKeyProtoUtil;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.GenericPublicKey;
-import java.security.KeyPair;
-import java.security.PublicKey;
-import java.util.Arrays;
-import javax.crypto.SecretKey;
-import junit.framework.TestCase;
-
-/**
- * Android compatible tests for the {@link EnrollmentCryptoOps} class.
- */
-public class EnrollmentCryptoOpsTest extends TestCase {
-
- private static final long DEVICE_ID = 1234567890L;
- private static final byte[] GCM_REGISTRATION_ID = { -0x80, 0, -0x80, 0, -0x80, 0 };
- private static final String DEVICE_MODEL = "TEST DEVICE";
- private static final String LOCALE = "en";
- private static final byte[] SESSION_ID = { 5, 5, 4, 4, 3, 3, 2, 2, 1, 1 };
- private static final String OAUTH_TOKEN = "1/23456etc";
-
- @Override
- protected void setUp() throws Exception {
- KeyEncodingTest.installSunEcSecurityProviderIfNecessary();
- assertEquals(
- PublicKeyProtoUtil.isLegacyCryptoRequired(), KeyEncoding.isLegacyCryptoRequired());
- super.setUp();
- }
-
- @Override
- protected void tearDown() throws Exception {
- KeyEncoding.setSimulateLegacyCrypto(false);
- super.tearDown();
- }
-
-
- public void testSimulatedEnrollment() throws Exception {
- boolean isLegacy = KeyEncoding.isLegacyCryptoRequired();
- // Step 1: Server generates an ephemeral DH key pair, saves the private key, and sends
- // the public key to the client as server_ephemeral_key.
- KeyPair serverEphemeralKeyPair =
- EnrollmentCryptoOps.generateEnrollmentKeyAgreementKeyPair(isLegacy);
- byte[] savedServerPrivateKey =
- KeyEncoding.encodeKeyAgreementPrivateKey(serverEphemeralKeyPair.getPrivate());
- byte[] serverEphemeralKey = KeyEncoding.encodeKeyAgreementPublicKey(
- serverEphemeralKeyPair.getPublic());
-
- // Step 2a: Client generates an ephemeral DH key pair, and completes the DH key exchange
- // to derive the master key.
- KeyPair clientEphemeralKeyPair =
- EnrollmentCryptoOps.generateEnrollmentKeyAgreementKeyPair(isLegacy);
- byte[] clientEphemeralKey = KeyEncoding.encodeKeyAgreementPublicKey(
- clientEphemeralKeyPair.getPublic());
- SecretKey clientMasterKey = EnrollmentCryptoOps.doKeyAgreement(
- clientEphemeralKeyPair.getPrivate(),
- KeyEncoding.parseKeyAgreementPublicKey(serverEphemeralKey));
-
- // Step 2b: Client generates its user key pair, and fills in a GcmDeviceInfo message containing
- // the enrollment request (which includes the user public key).
- KeyPair userKeyPair = isLegacy ? PublicKeyProtoUtil.generateRSA2048KeyPair()
- : PublicKeyProtoUtil.generateEcP256KeyPair();
- GcmDeviceInfo clientInfo = createGcmDeviceInfo(userKeyPair.getPublic(), clientMasterKey);
-
- // Step 2c: Client signcrypts the enrollment request to the server, using a combination of the
- // master key and its user signing key.
- byte[] enrollmentMessage = EnrollmentCryptoOps.encryptEnrollmentMessage(
- clientInfo, clientMasterKey, userKeyPair.getPrivate());
-
-
- // Step 3a: Server receives the client's DH public key and completes the key exchange using
- // the saved DH private key.
- SecretKey serverMasterKey = EnrollmentCryptoOps.doKeyAgreement(
- KeyEncoding.parseKeyAgreementPrivateKey(savedServerPrivateKey, isLegacy),
- KeyEncoding.parseKeyAgreementPublicKey(clientEphemeralKey));
-
- // Step 3b: Server uses the exchanged master key to de-signcrypt the enrollment request
- // (which also provides the user public key in the clear).
- GcmDeviceInfo serverInfo = EnrollmentCryptoOps.decryptEnrollmentMessage(
- enrollmentMessage, serverMasterKey, isLegacy);
-
- // Verify that the server sees the client's original enrollment request
- assertTrue(Arrays.equals(clientInfo.toByteArray(), serverInfo.toByteArray()));
-
- // Confirm that the server can recover a valid user PublicKey from the enrollment
- PublicKey serverUserPublicKey = KeyEncoding.parseUserPublicKey(
- serverInfo.getUserPublicKey().toByteArray());
- assertTrue(serverUserPublicKey.equals(userKeyPair.getPublic()));
- }
-
- public void testSimulatedEnrollmentWithForcedLegacy() throws Exception {
- if (PublicKeyProtoUtil.isLegacyCryptoRequired()) {
- // We already test with legacy in this case
- return;
- }
- KeyEncoding.setSimulateLegacyCrypto(true);
- testSimulatedEnrollment();
- }
-
- private GcmDeviceInfo createGcmDeviceInfo(PublicKey userPublicKey, SecretKey masterKey) {
- // One possible method of generating a key handle:
- GenericPublicKey encodedUserPublicKey = PublicKeyProtoUtil.encodePublicKey(userPublicKey);
- byte[] keyHandle = EnrollmentCryptoOps.sha256(encodedUserPublicKey.toByteArray());
-
- return GcmDeviceInfo.newBuilder()
- .setAndroidDeviceId(DEVICE_ID)
- .setGcmRegistrationId(ByteString.copyFrom(GCM_REGISTRATION_ID))
- .setDeviceMasterKeyHash(
- ByteString.copyFrom(EnrollmentCryptoOps.getMasterKeyHash(masterKey)))
- .setUserPublicKey(ByteString.copyFrom(KeyEncoding.encodeUserPublicKey(userPublicKey)))
- .setDeviceModel(DEVICE_MODEL)
- .setLocale(LOCALE)
- .setKeyHandle(ByteString.copyFrom(keyHandle))
- .setEnrollmentSessionId(ByteString.copyFrom(SESSION_ID))
- .setOauthToken(OAUTH_TOKEN)
- .build();
- }
-}
diff --git a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/KeyEncodingTest.java b/src/main/javatest/com/google/security/cryptauth/lib/securegcm/KeyEncodingTest.java
deleted file mode 100644
index 7012eae..0000000
--- a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/KeyEncodingTest.java
+++ /dev/null
@@ -1,189 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.security.cryptauth.lib.securegcm;
-
-import com.google.security.cryptauth.lib.securemessage.PublicKeyProtoUtil;
-import java.security.Key;
-import java.security.KeyPair;
-import java.security.PrivateKey;
-import java.security.Provider;
-import java.security.PublicKey;
-import java.security.Security;
-import java.security.interfaces.ECPrivateKey;
-import java.security.interfaces.ECPublicKey;
-import java.security.interfaces.RSAPrivateKey;
-import java.security.interfaces.RSAPublicKey;
-import java.security.spec.InvalidKeySpecException;
-import java.util.Arrays;
-import javax.crypto.interfaces.DHPrivateKey;
-import javax.crypto.interfaces.DHPublicKey;
-import junit.framework.TestCase;
-
-/**
- * Android compatible tests for the {@link KeyEncoding} class.
- */
-public class KeyEncodingTest extends TestCase {
- private static final byte[] RAW_KEY_BYTES = {
- 1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
- 1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
- 1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
- 1, 2};
-
- private Boolean isLegacy;
- private KeyPair userKeyPair;
-
- @Override
- protected void setUp() throws Exception {
- installSunEcSecurityProviderIfNecessary();
- isLegacy = PublicKeyProtoUtil.isLegacyCryptoRequired();
- setUserKeyPair();
- super.setUp();
- }
-
- @Override
- protected void tearDown() throws Exception {
- KeyEncoding.setSimulateLegacyCrypto(false);
- isLegacy = PublicKeyProtoUtil.isLegacyCryptoRequired();
- super.tearDown();
- }
-
- private void setUserKeyPair() {
- userKeyPair = isLegacy ? PublicKeyProtoUtil.generateRSA2048KeyPair()
- : PublicKeyProtoUtil.generateEcP256KeyPair();
- }
-
- public void testSimulateLegacyCrypto() {
- if (isLegacy) {
- return; // Nothing to test if we are already stuck in a legacy platform
- }
- assertFalse(KeyEncoding.isLegacyCryptoRequired());
- KeyEncoding.setSimulateLegacyCrypto(true);
- assertTrue(KeyEncoding.isLegacyCryptoRequired());
- }
-
- public void testMasterKeyEncoding() {
- // Require that master keys are encoded/decoded as raw byte arrays
- assertTrue(Arrays.equals(
- RAW_KEY_BYTES,
- KeyEncoding.encodeMasterKey(KeyEncoding.parseMasterKey(RAW_KEY_BYTES))));
- }
-
- public void testUserPublicKeyEncoding() throws InvalidKeySpecException {
- PublicKey pk = userKeyPair.getPublic();
- byte[] encodedPk = KeyEncoding.encodeUserPublicKey(pk);
- PublicKey decodedPk = KeyEncoding.parseUserPublicKey(encodedPk);
- assertKeysEqual(pk, decodedPk);
- }
-
- public void testUserPrivateKeyEncoding() throws InvalidKeySpecException {
- PrivateKey sk = userKeyPair.getPrivate();
- byte[] encodedSk = KeyEncoding.encodeUserPrivateKey(sk);
- PrivateKey decodedSk = KeyEncoding.parseUserPrivateKey(encodedSk, isLegacy);
- assertKeysEqual(sk, decodedSk);
- }
-
- public void testKeyAgreementPublicKeyEncoding() throws InvalidKeySpecException {
- KeyPair clientKeyPair = EnrollmentCryptoOps.generateEnrollmentKeyAgreementKeyPair(isLegacy);
- PublicKey pk = clientKeyPair.getPublic();
- byte[] encodedPk = KeyEncoding.encodeKeyAgreementPublicKey(pk);
- PublicKey decodedPk = KeyEncoding.parseKeyAgreementPublicKey(encodedPk);
- assertKeysEqual(pk, decodedPk);
- }
-
- public void testKeyAgreementPrivateKeyEncoding() throws InvalidKeySpecException {
- KeyPair clientKeyPair = EnrollmentCryptoOps.generateEnrollmentKeyAgreementKeyPair(isLegacy);
- PrivateKey sk = clientKeyPair.getPrivate();
- byte[] encodedSk = KeyEncoding.encodeKeyAgreementPrivateKey(sk);
- PrivateKey decodedSk = KeyEncoding.parseKeyAgreementPrivateKey(encodedSk, isLegacy);
- assertKeysEqual(sk, decodedSk);
- }
-
- public void testEncodingsWithForcedLegacy() throws InvalidKeySpecException {
- if (PublicKeyProtoUtil.isLegacyCryptoRequired()) {
- // We already test with legacy in this case
- return;
- }
- KeyEncoding.setSimulateLegacyCrypto(true);
- isLegacy = true;
- setUserKeyPair();
- testUserPublicKeyEncoding();
- testUserPrivateKeyEncoding();
- testKeyAgreementPublicKeyEncoding();
- testKeyAgreementPrivateKeyEncoding();
- }
-
- public void testSigningPublicKeyEncoding() throws InvalidKeySpecException {
- KeyPair keyPair = PublicKeyProtoUtil.generateEcP256KeyPair();
- PublicKey pk = keyPair.getPublic();
- byte[] encodedPk = KeyEncoding.encodeSigningPublicKey(pk);
- PublicKey decodedPk = KeyEncoding.parseSigningPublicKey(encodedPk);
- assertKeysEqual(pk, decodedPk);
- }
-
- public void testSigningPrivateKeyEncoding() throws InvalidKeySpecException {
- KeyPair keyPair = PublicKeyProtoUtil.generateEcP256KeyPair();
- PrivateKey sk = keyPair.getPrivate();
- byte[] encodedSk = KeyEncoding.encodeSigningPrivateKey(sk);
- PrivateKey decodedSk = KeyEncoding.parseSigningPrivateKey(encodedSk);
- assertKeysEqual(sk, decodedSk);
- }
-
- public void testDeviceSyncPublicKeyEncoding() throws InvalidKeySpecException {
- KeyPair keyPair = PublicKeyProtoUtil.generateEcP256KeyPair();
- PublicKey pk = keyPair.getPublic();
- byte[] encodedPk = KeyEncoding.encodeDeviceSyncGroupPublicKey(pk);
- PublicKey decodedPk = KeyEncoding.parseDeviceSyncGroupPublicKey(encodedPk);
- assertKeysEqual(pk, decodedPk);
- }
-
- void assertKeysEqual(Key a, Key b) {
- if ((a instanceof ECPublicKey)
- || (a instanceof ECPrivateKey)
- || (a instanceof RSAPublicKey)
- || (a instanceof RSAPrivateKey)) {
- assertNotNull(a.getEncoded());
- assertTrue(Arrays.equals(a.getEncoded(), b.getEncoded()));
- }
- if (a instanceof DHPublicKey) {
- DHPublicKey ya = (DHPublicKey) a;
- DHPublicKey yb = (DHPublicKey) b;
- assertEquals(ya.getY(), yb.getY());
- assertEquals(ya.getParams().getG(), yb.getParams().getG());
- assertEquals(ya.getParams().getP(), yb.getParams().getP());
- }
- if (a instanceof DHPrivateKey) {
- DHPrivateKey xa = (DHPrivateKey) a;
- DHPrivateKey xb = (DHPrivateKey) b;
- assertEquals(xa.getX(), xb.getX());
- assertEquals(xa.getParams().getG(), xb.getParams().getG());
- assertEquals(xa.getParams().getP(), xb.getParams().getP());
- }
- }
-
- /**
- * Registers the SunEC security provider if no EC security providers are currently registered.
- */
- // TODO(shabsi): Remove this method when b/7891565 is fixed
- static void installSunEcSecurityProviderIfNecessary() {
- if (Security.getProviders("KeyPairGenerator.EC") == null) {
- try {
- Class<?> providerClass = Class.forName("sun.security.ec.SunEC");
- Security.addProvider((Provider) providerClass.newInstance());
- } catch (Exception e) {
- // SunEC is not available, nothing we can do
- }
- }
- }
-}
diff --git a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/TransportCryptoOpsTest.java b/src/main/javatest/com/google/security/cryptauth/lib/securegcm/TransportCryptoOpsTest.java
deleted file mode 100644
index 9e45c0a..0000000
--- a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/TransportCryptoOpsTest.java
+++ /dev/null
@@ -1,110 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.security.cryptauth.lib.securegcm;
-
-import com.google.security.cryptauth.lib.securegcm.SecureGcmProto.Tickle;
-import com.google.security.cryptauth.lib.securegcm.TransportCryptoOps.Payload;
-import com.google.security.cryptauth.lib.securegcm.TransportCryptoOps.PayloadType;
-import com.google.security.cryptauth.lib.securemessage.PublicKeyProtoUtil;
-import java.security.KeyPair;
-import java.security.PublicKey;
-import java.util.Arrays;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.SecretKeySpec;
-import junit.framework.TestCase;
-
-/**
- * Android compatible tests for the {@link TransportCryptoOps} class.
- */
-public class TransportCryptoOpsTest extends TestCase {
- private static final byte[] KEY_BYTES = {
- 1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
- 1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
- 1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
- 1, 2
- };
- private static final byte[] KEY_HANDLE = { 9 };
-
- private SecretKey masterKey;
-
- @Override
- protected void setUp() throws Exception {
- KeyEncodingTest.installSunEcSecurityProviderIfNecessary();
- masterKey = new SecretKeySpec(KEY_BYTES, "AES");
- super.setUp();
- }
-
- public void testServerMessage() throws Exception {
- long tickleExpiry = 12345L;
- Tickle tickle = Tickle.newBuilder()
- .setExpiryTime(tickleExpiry)
- .build();
-
- // Simulate sending a message
- byte[] signcryptedMessage = TransportCryptoOps.signcryptServerMessage(
- new Payload(PayloadType.TICKLE, tickle.toByteArray()),
- masterKey,
- KEY_HANDLE);
-
- // Simulate the process of receiving the message
- assertTrue(Arrays.equals(KEY_HANDLE, TransportCryptoOps.getKeyHandleFor(signcryptedMessage)));
- Payload received = TransportCryptoOps.verifydecryptServerMessage(signcryptedMessage, masterKey);
- assertEquals(PayloadType.TICKLE, received.getPayloadType());
- Tickle receivedTickle = Tickle.parseFrom(received.getMessage());
- assertEquals(tickleExpiry, receivedTickle.getExpiryTime());
- }
-
- public void testClientMessage() throws Exception {
- if (PublicKeyProtoUtil.isLegacyCryptoRequired()) {
- return; // This test isn't for legacy crypto
- }
- KeyPair userKeyPair = PublicKeyProtoUtil.generateEcP256KeyPair();
- doTestClientMessageWith(userKeyPair);
- }
-
- public void testClientMessageWithLegacyCrypto() throws Exception {
- KeyPair userKeyPair = PublicKeyProtoUtil.generateRSA2048KeyPair();
- doTestClientMessageWith(userKeyPair);
- }
-
- private void doTestClientMessageWith(KeyPair userKeyPair) throws Exception {
- PublicKey userPublicKey = userKeyPair.getPublic();
- // Will use a Tickle for the test message, even though that would normally
- // only be sent from the server to the client
- long tickleExpiry = 12345L;
- Tickle tickle = Tickle.newBuilder()
- .setExpiryTime(tickleExpiry)
- .build();
-
- // Simulate sending a message
- byte[] signcryptedMessage = TransportCryptoOps.signcryptClientMessage(
- new Payload(PayloadType.TICKLE, tickle.toByteArray()),
- userKeyPair,
- masterKey);
-
- // Simulate the process of receiving the message
- byte[] encodedUserPublicKey = TransportCryptoOps.getEncodedUserPublicKeyFor(signcryptedMessage);
- assertTrue(Arrays.equals(KeyEncoding.encodeUserPublicKey(userPublicKey), encodedUserPublicKey));
- userPublicKey = KeyEncoding.parseUserPublicKey(encodedUserPublicKey);
- // At this point the server would have looked up the masterKey for this userPublicKey
-
- Payload received = TransportCryptoOps.verifydecryptClientMessage(
- signcryptedMessage, userPublicKey, masterKey);
-
- assertEquals(PayloadType.TICKLE, received.getPayloadType());
- Tickle receivedTickle = Tickle.parseFrom(received.getMessage());
- assertEquals(tickleExpiry, receivedTickle.getExpiryTime());
- }
-}
diff --git a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/Ukey2CppCompatibilityTest.java b/src/main/javatest/com/google/security/cryptauth/lib/securegcm/Ukey2CppCompatibilityTest.java
deleted file mode 100644
index db319e0..0000000
--- a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/Ukey2CppCompatibilityTest.java
+++ /dev/null
@@ -1,124 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.security.cryptauth.lib.securegcm;
-
-import com.google.security.cryptauth.lib.securegcm.Ukey2Handshake.HandshakeCipher;
-import com.google.security.cryptauth.lib.securegcm.Ukey2ShellCppWrapper.Mode;
-import java.util.Arrays;
-import junit.framework.TestCase;
-
-/**
- * Tests the compatibility between the Java and C++ implementations of the UKEY2 protocol. This
- * integration test executes and talks to a compiled binary exposing the C++ implementation (wrapped
- * by {@link Ukey2ShellCppWrapper}).
- *
- * <p>The C++ implementation is located in //security/cryptauth/lib/securegcm.
- */
-public class Ukey2CppCompatibilityTest extends TestCase {
- private static final int VERIFICATION_STRING_LENGTH = 32;
-
- private static final byte[] sPayload1 = "payload to encrypt1".getBytes();
- private static final byte[] sPayload2 = "payload to encrypt2".getBytes();
-
- /** Tests full handshake with C++ client and Java server. */
- public void testCppClientJavaServer() throws Exception {
- Ukey2ShellCppWrapper cppUkey2Shell =
- new Ukey2ShellCppWrapper(Mode.INITIATOR, VERIFICATION_STRING_LENGTH);
- cppUkey2Shell.startShell();
- Ukey2Handshake javaUkey2Handshake = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
-
- // ClientInit:
- byte[] clientInit = cppUkey2Shell.readHandshakeMessage();
- javaUkey2Handshake.parseHandshakeMessage(clientInit);
-
- // ServerInit:
- byte[] serverInit = javaUkey2Handshake.getNextHandshakeMessage();
- cppUkey2Shell.writeHandshakeMessage(serverInit);
-
- // ClientFinished:
- byte[] clientFinished = cppUkey2Shell.readHandshakeMessage();
- javaUkey2Handshake.parseHandshakeMessage(clientFinished);
-
- // Verification String:
- cppUkey2Shell.confirmAuthString(
- javaUkey2Handshake.getVerificationString(VERIFICATION_STRING_LENGTH));
- javaUkey2Handshake.verifyHandshake();
-
- // Secure channel:
- D2DConnectionContext javaSecureContext = javaUkey2Handshake.toConnectionContext();
-
- // ukey2_shell encodes data:
- byte[] encodedData = cppUkey2Shell.sendEncryptCommand(sPayload1);
- byte[] decodedData = javaSecureContext.decodeMessageFromPeer(encodedData);
- assertTrue(Arrays.equals(sPayload1, decodedData));
-
- // ukey2_shell decodes data:
- encodedData = javaSecureContext.encodeMessageToPeer(sPayload2);
- decodedData = cppUkey2Shell.sendDecryptCommand(encodedData);
- assertTrue(Arrays.equals(sPayload2, decodedData));
-
- // ukey2_shell session unique:
- byte[] localSessionUnique = javaSecureContext.getSessionUnique();
- byte[] remoteSessionUnique = cppUkey2Shell.sendSessionUniqueCommand();
- assertTrue(Arrays.equals(localSessionUnique, remoteSessionUnique));
-
- cppUkey2Shell.stopShell();
- }
-
- /** Tests full handshake with C++ server and Java client. */
- public void testCppServerJavaClient() throws Exception {
- Ukey2ShellCppWrapper cppUkey2Shell =
- new Ukey2ShellCppWrapper(Mode.RESPONDER, VERIFICATION_STRING_LENGTH);
- cppUkey2Shell.startShell();
- Ukey2Handshake javaUkey2Handshake = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
-
- // ClientInit:
- byte[] clientInit = javaUkey2Handshake.getNextHandshakeMessage();
- cppUkey2Shell.writeHandshakeMessage(clientInit);
-
- // ServerInit:
- byte[] serverInit = cppUkey2Shell.readHandshakeMessage();
- javaUkey2Handshake.parseHandshakeMessage(serverInit);
-
- // ClientFinished:
- byte[] clientFinished = javaUkey2Handshake.getNextHandshakeMessage();
- cppUkey2Shell.writeHandshakeMessage(clientFinished);
-
- // Verification String:
- cppUkey2Shell.confirmAuthString(
- javaUkey2Handshake.getVerificationString(VERIFICATION_STRING_LENGTH));
- javaUkey2Handshake.verifyHandshake();
-
- // Secure channel:
- D2DConnectionContext javaSecureContext = javaUkey2Handshake.toConnectionContext();
-
- // ukey2_shell encodes data:
- byte[] encodedData = cppUkey2Shell.sendEncryptCommand(sPayload1);
- byte[] decodedData = javaSecureContext.decodeMessageFromPeer(encodedData);
- assertTrue(Arrays.equals(sPayload1, decodedData));
-
- // ukey2_shell decodes data:
- encodedData = javaSecureContext.encodeMessageToPeer(sPayload2);
- decodedData = cppUkey2Shell.sendDecryptCommand(encodedData);
- assertTrue(Arrays.equals(sPayload2, decodedData));
-
- // ukey2_shell session unique:
- byte[] localSessionUnique = javaSecureContext.getSessionUnique();
- byte[] remoteSessionUnique = cppUkey2Shell.sendSessionUniqueCommand();
- assertTrue(Arrays.equals(localSessionUnique, remoteSessionUnique));
-
- cppUkey2Shell.stopShell();
- }
-}
diff --git a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/Ukey2HandshakeTest.java b/src/main/javatest/com/google/security/cryptauth/lib/securegcm/Ukey2HandshakeTest.java
deleted file mode 100644
index 49e6f30..0000000
--- a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/Ukey2HandshakeTest.java
+++ /dev/null
@@ -1,818 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.security.cryptauth.lib.securegcm;
-
-import com.google.protobuf.ByteString;
-import com.google.security.cryptauth.lib.securegcm.Ukey2Handshake.AlertException;
-import com.google.security.cryptauth.lib.securegcm.Ukey2Handshake.HandshakeCipher;
-import com.google.security.cryptauth.lib.securegcm.Ukey2Handshake.State;
-import com.google.security.cryptauth.lib.securegcm.UkeyProto.Ukey2ClientFinished;
-import com.google.security.cryptauth.lib.securegcm.UkeyProto.Ukey2ClientInit;
-import com.google.security.cryptauth.lib.securegcm.UkeyProto.Ukey2ClientInit.CipherCommitment;
-import com.google.security.cryptauth.lib.securegcm.UkeyProto.Ukey2Message;
-import com.google.security.cryptauth.lib.securegcm.UkeyProto.Ukey2ServerInit;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
-import junit.framework.TestCase;
-import org.junit.Assert;
-
-/**
- * Android compatible tests for the {@link Ukey2Handshake} class.
- */
-public class Ukey2HandshakeTest extends TestCase {
-
- private static final int MAX_AUTH_STRING_LENGTH = 32;
-
- @Override
- protected void setUp() throws Exception {
- KeyEncodingTest.installSunEcSecurityProviderIfNecessary();
- super.setUp();
- }
-
- /**
- * Tests correct use
- */
- public void testHandshake() throws Exception {
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
- Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
- byte[] handshakeMessage;
-
- assertEquals(State.IN_PROGRESS, client.getHandshakeState());
- assertEquals(State.IN_PROGRESS, server.getHandshakeState());
-
- // Message 1 (Client Init)
- handshakeMessage = client.getNextHandshakeMessage();
- server.parseHandshakeMessage(handshakeMessage);
- assertEquals(State.IN_PROGRESS, client.getHandshakeState());
- assertEquals(State.IN_PROGRESS, server.getHandshakeState());
-
- // Message 2 (Server Init)
- handshakeMessage = server.getNextHandshakeMessage();
- client.parseHandshakeMessage(handshakeMessage);
- assertEquals(State.IN_PROGRESS, client.getHandshakeState());
- assertEquals(State.IN_PROGRESS, server.getHandshakeState());
-
- // Message 3 (Client Finish)
- handshakeMessage = client.getNextHandshakeMessage();
- server.parseHandshakeMessage(handshakeMessage);
- assertEquals(State.VERIFICATION_NEEDED, client.getHandshakeState());
- assertEquals(State.VERIFICATION_NEEDED, server.getHandshakeState());
-
- // Get the auth string
- byte[] clientAuthString = client.getVerificationString(MAX_AUTH_STRING_LENGTH);
- byte[] serverAuthString = server.getVerificationString(MAX_AUTH_STRING_LENGTH);
- Assert.assertArrayEquals(clientAuthString, serverAuthString);
- assertEquals(State.VERIFICATION_IN_PROGRESS, client.getHandshakeState());
- assertEquals(State.VERIFICATION_IN_PROGRESS, server.getHandshakeState());
-
- // Verify the auth string
- client.verifyHandshake();
- server.verifyHandshake();
- assertEquals(State.FINISHED, client.getHandshakeState());
- assertEquals(State.FINISHED, server.getHandshakeState());
-
- // Make a context
- D2DConnectionContext clientContext = client.toConnectionContext();
- D2DConnectionContext serverContext = server.toConnectionContext();
- assertContextsCompatible(clientContext, serverContext);
- assertEquals(State.ALREADY_USED, client.getHandshakeState());
- assertEquals(State.ALREADY_USED, server.getHandshakeState());
- }
-
- /**
- * Verify enums for ciphers match the proto values
- */
- public void testCipherEnumValuesCorrect() {
- assertEquals(
- "You added a cipher, but forgot to change the test", 1, HandshakeCipher.values().length);
-
- assertEquals(UkeyProto.Ukey2HandshakeCipher.P256_SHA512,
- HandshakeCipher.P256_SHA512.getValue());
- }
-
- /**
- * Tests incorrect use by callers (client and servers accidentally sending the wrong message at
- * the wrong time)
- */
- public void testHandshakeClientAndServerSendRepeatedOutOfOrderMessages() throws Exception {
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- // Client sends ClientInit (again) instead of ClientFinished
- Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
- Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
- byte[] handshakeMessage = client.getNextHandshakeMessage();
- server.parseHandshakeMessage(handshakeMessage);
- server.getNextHandshakeMessage(); // do this to avoid illegal state
- try {
- server.parseHandshakeMessage(handshakeMessage);
- fail("Expected Alert for client sending ClientInit twice");
- } catch (HandshakeException e) {
- // success
- }
- assertEquals(State.ERROR, server.getHandshakeState());
-
- // Server sends ClientInit back to client instead of ServerInit
- client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
- server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
- handshakeMessage = client.getNextHandshakeMessage();
- try {
- client.parseHandshakeMessage(handshakeMessage);
- fail("Expected Alert for server sending ClientInit back to client");
- } catch (AlertException e) {
- // success
- }
- assertEquals(State.ERROR, client.getHandshakeState());
-
- // Clients sends ServerInit back to client instead of ClientFinished
- client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
- server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
- handshakeMessage = client.getNextHandshakeMessage();
- server.parseHandshakeMessage(handshakeMessage);
- handshakeMessage = server.getNextHandshakeMessage();
- try {
- server.parseHandshakeMessage(handshakeMessage);
- fail("Expected Alert for client sending ServerInit back to server");
- } catch (HandshakeException e) {
- // success
- }
- assertEquals(State.ERROR, server.getHandshakeState());
- }
-
- /**
- * Tests that verification codes are different for different handshake runs. Also tests a full
- * on-path attack.
- */
- public void testVerificationCodeUniqueToSession() throws Exception {
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- // Client 1 and Server 1
- Ukey2Handshake client1 = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
- Ukey2Handshake server1 = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
- byte[] handshakeMessage = client1.getNextHandshakeMessage();
- server1.parseHandshakeMessage(handshakeMessage);
- handshakeMessage = server1.getNextHandshakeMessage();
- client1.parseHandshakeMessage(handshakeMessage);
- handshakeMessage = client1.getNextHandshakeMessage();
- server1.parseHandshakeMessage(handshakeMessage);
- byte[] client1AuthString = client1.getVerificationString(MAX_AUTH_STRING_LENGTH);
- byte[] server1AuthString = server1.getVerificationString(MAX_AUTH_STRING_LENGTH);
- Assert.assertArrayEquals(client1AuthString, server1AuthString);
-
- // Client 2 and Server 2
- Ukey2Handshake client2 = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
- Ukey2Handshake server2 = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
- handshakeMessage = client2.getNextHandshakeMessage();
- server2.parseHandshakeMessage(handshakeMessage);
- handshakeMessage = server2.getNextHandshakeMessage();
- client2.parseHandshakeMessage(handshakeMessage);
- handshakeMessage = client2.getNextHandshakeMessage();
- server2.parseHandshakeMessage(handshakeMessage);
- byte[] client2AuthString = client2.getVerificationString(MAX_AUTH_STRING_LENGTH);
- byte[] server2AuthString = server2.getVerificationString(MAX_AUTH_STRING_LENGTH);
- Assert.assertArrayEquals(client2AuthString, server2AuthString);
-
- // Make sure the verification strings differ
- assertFalse(Arrays.equals(client1AuthString, client2AuthString));
- }
-
- /**
- * Test an attack where the adversary swaps out the public key in the final message (i.e.,
- * commitment doesn't match public key)
- */
- public void testPublicKeyDoesntMatchCommitment() throws Exception {
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- // Run handshake as usual, but stop before sending client finished
- Ukey2Handshake client1 = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
- Ukey2Handshake server1 = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
- byte[] handshakeMessage = client1.getNextHandshakeMessage();
- server1.parseHandshakeMessage(handshakeMessage);
- handshakeMessage = server1.getNextHandshakeMessage();
-
- // Run another handshake and get the final client finished
- Ukey2Handshake client2 = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
- Ukey2Handshake server2 = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
- handshakeMessage = client2.getNextHandshakeMessage();
- server2.parseHandshakeMessage(handshakeMessage);
- handshakeMessage = server2.getNextHandshakeMessage();
- client2.parseHandshakeMessage(handshakeMessage);
- handshakeMessage = client2.getNextHandshakeMessage();
-
- // Now use the client finished from second handshake in first handshake (simulates where an
- // attacker switches out the last message).
- try {
- server1.parseHandshakeMessage(handshakeMessage);
- fail("Expected server to catch mismatched ClientFinished");
- } catch (HandshakeException e) {
- // success
- }
- assertEquals(State.ERROR, server1.getHandshakeState());
-
- // Make sure caller can't actually do anything with the server now that an error has occurred
- try {
- server1.getVerificationString(MAX_AUTH_STRING_LENGTH);
- fail("Server allows operations post error");
- } catch (IllegalStateException e) {
- // success
- }
- try {
- server1.verifyHandshake();
- fail("Server allows operations post error");
- } catch (IllegalStateException e) {
- // success
- }
- try {
- server1.toConnectionContext();
- fail("Server allows operations post error");
- } catch (IllegalStateException e) {
- // success
- }
- }
-
- /**
- * Test commitment having unsupported version
- */
- public void testClientInitUnsupportedVersion() throws Exception {
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- // Get ClientInit and modify the version to be too big
- Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
- Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
- byte[] handshakeMessage = client.getNextHandshakeMessage();
-
- Ukey2Message.Builder message = Ukey2Message.newBuilder(
- Ukey2Message.parseFrom(handshakeMessage));
- Ukey2ClientInit.Builder clientInit =
- Ukey2ClientInit.newBuilder(Ukey2ClientInit.parseFrom(message.getMessageData()));
- clientInit.setVersion(Ukey2Handshake.VERSION + 1);
- message.setMessageData(ByteString.copyFrom(clientInit.build().toByteArray()));
- handshakeMessage = message.build().toByteArray();
-
- try {
- server.parseHandshakeMessage(handshakeMessage);
- fail("Server did not catch unsupported version (too big) in ClientInit");
- } catch (AlertException e) {
- // success
- }
- assertEquals(State.ERROR, server.getHandshakeState());
-
- // Get ClientInit and modify the version to be too big
- client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
- server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
- handshakeMessage = client.getNextHandshakeMessage();
-
- message = Ukey2Message.newBuilder(
- Ukey2Message.parseFrom(handshakeMessage));
- clientInit = Ukey2ClientInit.newBuilder(Ukey2ClientInit.parseFrom(message.getMessageData()));
- clientInit.setVersion(0 /* minimum version is 1 */);
- message.setMessageData(ByteString.copyFrom(clientInit.build().toByteArray()));
- handshakeMessage = message.build().toByteArray();
-
- try {
- server.parseHandshakeMessage(handshakeMessage);
- fail("Server did not catch unsupported version (too small) in ClientInit");
- } catch (AlertException e) {
- // success
- }
- assertEquals(State.ERROR, server.getHandshakeState());
- }
-
- /**
- * Tests that server catches wrong number of random bytes in ClientInit
- */
- public void testWrongNonceLengthInClientInit() throws Exception {
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- // Get ClientInit and modify the nonce
- Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
- Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
- byte[] handshakeMessage = client.getNextHandshakeMessage();
-
- Ukey2Message.Builder message = Ukey2Message.newBuilder(
- Ukey2Message.parseFrom(handshakeMessage));
- Ukey2ClientInit.Builder clientInit =
- Ukey2ClientInit.newBuilder(Ukey2ClientInit.parseFrom(message.getMessageData()));
- clientInit.setRandom(
- ByteString.copyFrom(
- Arrays.copyOf(
- clientInit.getRandom().toByteArray(),
- 31 /* as per go/ukey2, nonces must be 32 bytes long */)));
- message.setMessageData(ByteString.copyFrom(clientInit.build().toByteArray()));
- handshakeMessage = message.build().toByteArray();
-
- try {
- server.parseHandshakeMessage(handshakeMessage);
- fail("Server did not catch nonce being too short in ClientInit");
- } catch (AlertException e) {
- // success
- }
- assertEquals(State.ERROR, server.getHandshakeState());
- }
-
- /**
- * Test that server catches missing commitment in ClientInit message
- */
- public void testServerCatchesMissingCommitmentInClientInit() throws Exception {
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- // Get ClientInit and modify the commitment
- Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
- Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
- byte[] handshakeMessage = client.getNextHandshakeMessage();
-
- Ukey2Message.Builder message = Ukey2Message.newBuilder(
- Ukey2Message.parseFrom(handshakeMessage));
- Ukey2ClientInit clientInit =
- Ukey2ClientInit.newBuilder(Ukey2ClientInit.parseFrom(message.getMessageData()))
- .build();
- Ukey2ClientInit.Builder badClientInit = Ukey2ClientInit.newBuilder()
- .setVersion(clientInit.getVersion())
- .setRandom(clientInit.getRandom());
- for (CipherCommitment commitment : clientInit.getCipherCommitmentsList()) {
- badClientInit.addCipherCommitments(commitment);
- }
-
- message.setMessageData(ByteString.copyFrom(badClientInit.build().toByteArray()));
- handshakeMessage = message.build().toByteArray();
-
- try {
- server.parseHandshakeMessage(handshakeMessage);
- fail("Server did not catch missing commitment in ClientInit");
- } catch (AlertException e) {
- // success
- }
- }
-
- /**
- * Test that client catches invalid version in ServerInit
- */
- public void testServerInitUnsupportedVersion() throws Exception {
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- // Get ServerInit and modify the version to be too big
- Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
- Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
- byte[] handshakeMessage = client.getNextHandshakeMessage();
- server.parseHandshakeMessage(handshakeMessage);
- handshakeMessage = server.getNextHandshakeMessage();
-
- Ukey2Message.Builder message = Ukey2Message.newBuilder(
- Ukey2Message.parseFrom(handshakeMessage));
- Ukey2ServerInit serverInit =
- Ukey2ServerInit.newBuilder(Ukey2ServerInit.parseFrom(message.getMessageData()))
- .setVersion(Ukey2Handshake.VERSION + 1)
- .build();
- message.setMessageData(ByteString.copyFrom(serverInit.toByteArray()));
- handshakeMessage = message.build().toByteArray();
-
- try {
- client.parseHandshakeMessage(handshakeMessage);
- fail("Client did not catch unsupported version (too big) in ServerInit");
- } catch (AlertException e) {
- // success
- }
- assertEquals(State.ERROR, client.getHandshakeState());
-
- // Get ServerInit and modify the version to be too big
- client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
- server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
- handshakeMessage = client.getNextHandshakeMessage();
- server.parseHandshakeMessage(handshakeMessage);
- handshakeMessage = server.getNextHandshakeMessage();
-
- message = Ukey2Message.newBuilder(Ukey2Message.parseFrom(handshakeMessage));
- serverInit =
- Ukey2ServerInit.newBuilder(Ukey2ServerInit.parseFrom(message.getMessageData()))
- .setVersion(0 /* minimum version is 1 */)
- .build();
- message.setMessageData(ByteString.copyFrom(serverInit.toByteArray()));
- handshakeMessage = message.build().toByteArray();
-
- try {
- client.parseHandshakeMessage(handshakeMessage);
- fail("Client did not catch unsupported version (too small) in ServerInit");
- } catch (AlertException e) {
- // success
- }
- assertEquals(State.ERROR, client.getHandshakeState());
- }
-
- /**
- * Tests that client catches wrong number of random bytes in ServerInit
- */
- public void testWrongNonceLengthInServerInit() throws Exception {
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- // Get ServerInit and modify the nonce
- Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
- Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
- byte[] handshakeMessage = client.getNextHandshakeMessage();
- server.parseHandshakeMessage(handshakeMessage);
- handshakeMessage = server.getNextHandshakeMessage();
-
- Ukey2Message.Builder message = Ukey2Message.newBuilder(
- Ukey2Message.parseFrom(handshakeMessage));
- Ukey2ServerInit.Builder serverInitBuilder =
- Ukey2ServerInit.newBuilder(Ukey2ServerInit.parseFrom(message.getMessageData()));
- Ukey2ServerInit serverInit = serverInitBuilder.setRandom(ByteString.copyFrom(Arrays.copyOf(
- serverInitBuilder.getRandom().toByteArray(),
- 31 /* as per go/ukey2, nonces must be 32 bytes long */)))
- .build();
- message.setMessageData(ByteString.copyFrom(serverInit.toByteArray()));
- handshakeMessage = message.build().toByteArray();
-
- try {
- client.parseHandshakeMessage(handshakeMessage);
- fail("Client did not catch nonce being too short in ServerInit");
- } catch (AlertException e) {
- // success
- }
- assertEquals(State.ERROR, client.getHandshakeState());
- }
-
- /**
- * Test that client catches missing or incorrect handshake cipher in serverInit
- */
- public void testMissingOrIncorrectHandshakeCipherInServerInit() throws Exception {
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- // Get ServerInit
- Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
- Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
- byte[] handshakeMessage = client.getNextHandshakeMessage();
- server.parseHandshakeMessage(handshakeMessage);
- handshakeMessage = server.getNextHandshakeMessage();
- Ukey2Message.Builder message = Ukey2Message.newBuilder(
- Ukey2Message.parseFrom(handshakeMessage));
- Ukey2ServerInit serverInit = Ukey2ServerInit.parseFrom(message.getMessageData());
-
- // remove handshake cipher
- Ukey2ServerInit badServerInit = Ukey2ServerInit.newBuilder()
- .setPublicKey(serverInit.getPublicKey())
- .setRandom(serverInit.getRandom())
- .setVersion(serverInit.getVersion())
- .build();
-
- message.setMessageData(ByteString.copyFrom(badServerInit.toByteArray()));
- handshakeMessage = message.build().toByteArray();
-
- try {
- client.parseHandshakeMessage(handshakeMessage);
- fail("Client did not catch missing handshake cipher in ServerInit");
- } catch (AlertException e) {
- // success
- }
- assertEquals(State.ERROR, client.getHandshakeState());
-
- // Get ServerInit
- client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
- server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
- handshakeMessage = client.getNextHandshakeMessage();
- server.parseHandshakeMessage(handshakeMessage);
- handshakeMessage = server.getNextHandshakeMessage();
- message = Ukey2Message.newBuilder(Ukey2Message.parseFrom(handshakeMessage));
- serverInit = Ukey2ServerInit.parseFrom(message.getMessageData());
-
- // put in a bad handshake cipher
- badServerInit = Ukey2ServerInit.newBuilder()
- .setPublicKey(serverInit.getPublicKey())
- .setRandom(serverInit.getRandom())
- .setVersion(serverInit.getVersion())
- .setHandshakeCipher(UkeyProto.Ukey2HandshakeCipher.RESERVED)
- .build();
-
- message.setMessageData(ByteString.copyFrom(badServerInit.toByteArray()));
- handshakeMessage = message.build().toByteArray();
-
- try {
- client.parseHandshakeMessage(handshakeMessage);
- fail("Client did not catch bad handshake cipher in ServerInit");
- } catch (AlertException e) {
- // success
- }
- assertEquals(State.ERROR, client.getHandshakeState());
- }
-
- /**
- * Test that client catches missing or incorrect public key in serverInit
- */
- public void testMissingOrIncorrectPublicKeyInServerInit() throws Exception {
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- // Get ServerInit
- Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
- Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
- byte[] handshakeMessage = client.getNextHandshakeMessage();
- server.parseHandshakeMessage(handshakeMessage);
- handshakeMessage = server.getNextHandshakeMessage();
- Ukey2Message.Builder message = Ukey2Message.newBuilder(
- Ukey2Message.parseFrom(handshakeMessage));
- Ukey2ServerInit serverInit = Ukey2ServerInit.parseFrom(message.getMessageData());
-
- // remove public key
- Ukey2ServerInit badServerInit = Ukey2ServerInit.newBuilder()
- .setRandom(serverInit.getRandom())
- .setVersion(serverInit.getVersion())
- .setHandshakeCipher(serverInit.getHandshakeCipher())
- .build();
-
- message.setMessageData(ByteString.copyFrom(badServerInit.toByteArray()));
- handshakeMessage = message.build().toByteArray();
-
- try {
- client.parseHandshakeMessage(handshakeMessage);
- fail("Client did not catch missing public key in ServerInit");
- } catch (AlertException e) {
- // success
- }
- assertEquals(State.ERROR, client.getHandshakeState());
-
- // Get ServerInit
- client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
- server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
- handshakeMessage = client.getNextHandshakeMessage();
- server.parseHandshakeMessage(handshakeMessage);
- handshakeMessage = server.getNextHandshakeMessage();
- message = Ukey2Message.newBuilder(
- Ukey2Message.parseFrom(handshakeMessage));
- serverInit = Ukey2ServerInit.parseFrom(message.getMessageData());
-
- // put in a bad public key
- badServerInit = Ukey2ServerInit.newBuilder()
- .setPublicKey(ByteString.copyFrom(new byte[] {42, 12, 1}))
- .setRandom(serverInit.getRandom())
- .setVersion(serverInit.getVersion())
- .setHandshakeCipher(serverInit.getHandshakeCipher())
- .build();
-
- message.setMessageData(ByteString.copyFrom(badServerInit.toByteArray()));
- handshakeMessage = message.build().toByteArray();
-
- try {
- client.parseHandshakeMessage(handshakeMessage);
- fail("Client did not catch bad public key in ServerInit");
- } catch (AlertException e) {
- // success
- }
- assertEquals(State.ERROR, client.getHandshakeState());
- }
-
- /**
- * Test that client catches missing or incorrect public key in clientFinished
- */
- public void testMissingOrIncorrectPublicKeyInClientFinished() throws Exception {
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- // Get ClientFinished
- Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
- Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
- byte[] handshakeMessage = client.getNextHandshakeMessage();
- server.parseHandshakeMessage(handshakeMessage);
- handshakeMessage = server.getNextHandshakeMessage();
- client.parseHandshakeMessage(handshakeMessage);
- handshakeMessage = client.getNextHandshakeMessage();
- Ukey2Message.Builder message = Ukey2Message.newBuilder(
- Ukey2Message.parseFrom(handshakeMessage));
-
- // remove public key
- Ukey2ClientFinished.Builder badClientFinished = Ukey2ClientFinished.newBuilder();
-
- message.setMessageData(ByteString.copyFrom(badClientFinished.build().toByteArray()));
- handshakeMessage = message.build().toByteArray();
-
- try {
- server.parseHandshakeMessage(handshakeMessage);
- fail("Server did not catch missing public key in ClientFinished");
- } catch (HandshakeException e) {
- // success
- }
- assertEquals(State.ERROR, server.getHandshakeState());
-
- // Get ClientFinished
- client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
- server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
- handshakeMessage = client.getNextHandshakeMessage();
- server.parseHandshakeMessage(handshakeMessage);
- handshakeMessage = server.getNextHandshakeMessage();
- client.parseHandshakeMessage(handshakeMessage);
- handshakeMessage = client.getNextHandshakeMessage();
- message = Ukey2Message.newBuilder(Ukey2Message.parseFrom(handshakeMessage));
-
- // remove public key
- badClientFinished = Ukey2ClientFinished.newBuilder()
- .setPublicKey(ByteString.copyFrom(new byte[] {42, 12, 1}));
-
- message.setMessageData(ByteString.copyFrom(badClientFinished.build().toByteArray()));
- handshakeMessage = message.build().toByteArray();
-
- try {
- server.parseHandshakeMessage(handshakeMessage);
- fail("Server did not catch bad public key in ClientFinished");
- } catch (HandshakeException e) {
- // success
- }
- assertEquals(State.ERROR, server.getHandshakeState());
- }
-
- /**
- * Tests that items (nonces, commitments, public keys) that should be random are at least
- * different on every run.
- */
- public void testRandomItemsDifferentOnEveryRun() throws Exception {
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- int numberOfRuns = 50;
-
- // Search for collisions
- Set<Integer> commitments = new HashSet<>(numberOfRuns);
- Set<Integer> clientNonces = new HashSet<>(numberOfRuns);
- Set<Integer> serverNonces = new HashSet<>(numberOfRuns);
- Set<Integer> serverPublicKeys = new HashSet<>(numberOfRuns);
- Set<Integer> clientPublicKeys = new HashSet<>(numberOfRuns);
-
- for (int i = 0; i < numberOfRuns; i++) {
- Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
- Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
- byte[] handshakeMessage = client.getNextHandshakeMessage();
- Ukey2Message message = Ukey2Message.parseFrom(handshakeMessage);
- Ukey2ClientInit clientInit = Ukey2ClientInit.parseFrom(message.getMessageData());
-
- server.parseHandshakeMessage(handshakeMessage);
- handshakeMessage = server.getNextHandshakeMessage();
- message = Ukey2Message.parseFrom(handshakeMessage);
- Ukey2ServerInit serverInit = Ukey2ServerInit.parseFrom(message.getMessageData());
-
- client.parseHandshakeMessage(handshakeMessage);
- handshakeMessage = client.getNextHandshakeMessage();
- message = Ukey2Message.parseFrom(handshakeMessage);
- Ukey2ClientFinished clientFinished = Ukey2ClientFinished.parseFrom(message.getMessageData());
-
- // Clean up to save some memory (b/32054837)
- client = null;
- server = null;
- handshakeMessage = null;
- message = null;
- System.gc();
-
- // ClientInit randomness
- Integer nonceHash = Integer.valueOf(Arrays.hashCode(clientInit.getRandom().toByteArray()));
- if (clientNonces.contains(nonceHash) || serverNonces.contains(nonceHash)) {
- fail("Nonce in ClientINit has repeated!");
- }
- clientNonces.add(nonceHash);
-
- Integer commitmentHash = 0;
- for (CipherCommitment commitement : clientInit.getCipherCommitmentsList()) {
- commitmentHash += Arrays.hashCode(commitement.toByteArray());
- }
- if (commitments.contains(nonceHash)) {
- fail("Commitment has repeated!");
- }
- commitments.add(commitmentHash);
-
- // ServerInit randomness
- nonceHash = Integer.valueOf(Arrays.hashCode(serverInit.getRandom().toByteArray()));
- if (serverNonces.contains(nonceHash) || clientNonces.contains(nonceHash)) {
- fail("Nonce in ServerInit repeated!");
- }
- serverNonces.add(nonceHash);
-
- Integer publicKeyHash =
- Integer.valueOf(Arrays.hashCode(serverInit.getPublicKey().toByteArray()));
- if (serverPublicKeys.contains(publicKeyHash) || clientPublicKeys.contains(publicKeyHash)) {
- fail("Public Key in ServerInit repeated!");
- }
- serverPublicKeys.add(publicKeyHash);
-
- // Client Finished randomness
- publicKeyHash = Integer.valueOf(Arrays.hashCode(clientFinished.getPublicKey().toByteArray()));
- if (serverPublicKeys.contains(publicKeyHash) || clientPublicKeys.contains(publicKeyHash)) {
- fail("Public Key in ClientFinished repeated!");
- }
- clientPublicKeys.add(publicKeyHash);
- }
- }
-
- /**
- * Tests that {@link Ukey2Handshake#getVerificationString(int)} enforces sane verification string
- * lengths.
- */
- public void testGetVerificationEnforcesSaneLengths() throws Exception {
- if (KeyEncoding.isLegacyCryptoRequired()) {
- // this means we're running on an old SDK, which doesn't support the
- // necessary crypto. Let's not test anything in this case.
- return;
- }
-
- // Run the protocol
- Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512);
- Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512);
- byte[] handshakeMessage = client.getNextHandshakeMessage();
- server.parseHandshakeMessage(handshakeMessage);
- handshakeMessage = server.getNextHandshakeMessage();
- client.parseHandshakeMessage(handshakeMessage);
- handshakeMessage = client.getNextHandshakeMessage();
- server.parseHandshakeMessage(handshakeMessage);
-
- // Try to get too short verification string
- try {
- client.getVerificationString(0);
- fail("Too short verification string allowed");
- } catch (IllegalArgumentException e) {
- // success
- }
-
- // Try to get too long verification string
- try {
- server.getVerificationString(MAX_AUTH_STRING_LENGTH + 1);
- fail("Too long verification string allowed");
- } catch (IllegalArgumentException e) {
- // success
- }
- }
-
- /**
- * Asserts that the given client and server contexts are compatible
- */
- private void assertContextsCompatible(
- D2DConnectionContext clientContext, D2DConnectionContext serverContext) {
- assertNotNull(clientContext);
- assertNotNull(serverContext);
- assertEquals(D2DConnectionContextV1.PROTOCOL_VERSION, clientContext.getProtocolVersion());
- assertEquals(D2DConnectionContextV1.PROTOCOL_VERSION, serverContext.getProtocolVersion());
- assertEquals(clientContext.getEncodeKey(), serverContext.getDecodeKey());
- assertEquals(clientContext.getDecodeKey(), serverContext.getEncodeKey());
- assertFalse(clientContext.getEncodeKey().equals(clientContext.getDecodeKey()));
- assertEquals(0, clientContext.getSequenceNumberForEncoding());
- assertEquals(0, clientContext.getSequenceNumberForDecoding());
- assertEquals(0, serverContext.getSequenceNumberForEncoding());
- assertEquals(0, serverContext.getSequenceNumberForDecoding());
- }
-}
diff --git a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/Ukey2ShellCppWrapper.java b/src/main/javatest/com/google/security/cryptauth/lib/securegcm/Ukey2ShellCppWrapper.java
deleted file mode 100644
index 2b73653..0000000
--- a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/Ukey2ShellCppWrapper.java
+++ /dev/null
@@ -1,342 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.security.cryptauth.lib.securegcm;
-
-import com.google.common.io.BaseEncoding;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.lang.ProcessBuilder.Redirect;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.util.Arrays;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import javax.annotation.Nullable;
-
-/**
- * A wrapper to execute and interact with the //security/cryptauth/lib/securegcm:ukey2_shell binary.
- *
- * <p>This binary is a shell over the C++ implementation of the UKEY2 protocol, so this wrapper is
- * used to test compatibility between the C++ and Java implementations.
- *
- * <p>The ukey2_shell is invoked as follows:
- *
- * <pre>{@code
- * ukey2_shell --mode=<mode> --verification_string_length=<length>
- * }</pre>
- *
- * where {@code mode={initiator, responder}} and {@code verification_string_length} is a positive
- * integer.
- */
-public class Ukey2ShellCppWrapper {
- // The path the the ukey2_shell binary.
- private static final String BINARY_PATH = "build/src/main/cpp/src/securegcm/ukey2_shell";
-
- // The time to wait before timing out a read or write operation to the shell.
- @SuppressWarnings("GoodTime") // TODO(b/147378611): store a java.time.Duration instead
- private static final long IO_TIMEOUT_MILLIS = 5000;
-
- public enum Mode {
- INITIATOR,
- RESPONDER
- }
-
- private final Mode mode;
- private final int verificationStringLength;
- private final ExecutorService executorService;
-
- @Nullable private Process shellProcess;
- private boolean secureContextEstablished;
-
- /**
- * @param mode The mode to run the shell in (initiator or responder).
- * @param verificationStringLength The length of the verification string used in the handshake.
- */
- public Ukey2ShellCppWrapper(Mode mode, int verificationStringLength) {
- this.mode = mode;
- this.verificationStringLength = verificationStringLength;
- this.executorService = Executors.newSingleThreadExecutor();
- }
-
- /**
- * Begins execution of the ukey2_shell binary.
- *
- * @throws IOException
- */
- public void startShell() throws IOException {
- if (shellProcess != null) {
- throw new IllegalStateException("Shell already started.");
- }
-
- String modeArg = "--mode=" + getModeString();
- String verificationStringLengthArg = "--verification_string_length=" + verificationStringLength;
-
- final ProcessBuilder builder =
- new ProcessBuilder(BINARY_PATH, modeArg, verificationStringLengthArg);
-
- // Merge the shell's stderr with the stderr of the current process.
- builder.redirectError(Redirect.INHERIT);
-
- shellProcess = builder.start();
- }
-
- /**
- * Stops execution of the ukey2_shell binary.
- *
- * @throws IOException
- */
- public void stopShell() {
- if (shellProcess == null) {
- throw new IllegalStateException("Shell not started.");
- }
- shellProcess.destroy();
- }
-
- /**
- * @return the handshake message read from the shell.
- * @throws IOException
- */
- public byte[] readHandshakeMessage() throws IOException {
- return readFrameWithTimeout();
- }
-
- /**
- * Sends the handshake message to the shell.
- *
- * @param message
- * @throws IOException
- */
- public void writeHandshakeMessage(byte[] message) throws IOException {
- writeFrameWithTimeout(message);
- }
-
- /**
- * Reads the auth string from the shell and compares it with {@code authString}. If verification
- * succeeds, then write "ok" back as a confirmation.
- *
- * @param authString the auth string to compare to.
- * @throws IOException
- */
- public void confirmAuthString(byte[] authString) throws IOException {
- byte[] shellAuthString = readFrameWithTimeout();
- if (!Arrays.equals(authString, shellAuthString)) {
- throw new IOException(
- String.format(
- "Unable to verify auth string: 0x%s != 0x%s",
- BaseEncoding.base16().encode(authString),
- BaseEncoding.base16().encode(shellAuthString)));
- }
- writeFrameWithTimeout("ok".getBytes());
- secureContextEstablished = true;
- }
-
- /**
- * Sends {@code payload} to be encrypted by the shell. This function can only be called after a
- * handshake is performed and a secure context established.
- *
- * @param payload the data to be encrypted.
- * @return the encrypted message returned by the shell.
- * @throws IOException
- */
- public byte[] sendEncryptCommand(byte[] payload) throws IOException {
- writeFrameWithTimeout(createExpression("encrypt", payload));
- return readFrameWithTimeout();
- }
-
- /**
- * Sends {@code message} to be decrypted by the shell. This function can only be called after a
- * handshake is performed and a secure context established.
- *
- * @param message the data to be decrypted.
- * @return the decrypted payload returned by the shell.
- * @throws IOException
- */
- public byte[] sendDecryptCommand(byte[] message) throws IOException {
- writeFrameWithTimeout(createExpression("decrypt", message));
- return readFrameWithTimeout();
- }
-
- /**
- * Requests the session unique value from the shell. This function can only be called after a
- * handshake is performed and a secure context established.
- *
- * @return the session unique value returned by the shell.
- * @throws IOException
- */
- public byte[] sendSessionUniqueCommand() throws IOException {
- writeFrameWithTimeout(createExpression("session_unique", null));
- return readFrameWithTimeout();
- }
-
- /**
- * Reads a frame from the shell's stdout with a timeout.
- *
- * @return The contents of the frame.
- * @throws IOException
- */
- private byte[] readFrameWithTimeout() throws IOException {
- Future<byte[]> future =
- executorService.submit(
- new Callable<byte[]>() {
- @Override
- public byte[] call() throws Exception {
- return readFrame();
- }
- });
-
- try {
- return future.get(IO_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
- } catch (InterruptedException | ExecutionException | TimeoutException e) {
- throw new IOException(e);
- }
- }
-
- /**
- * Writes a frame to the shell's stdin with a timeout.
- *
- * @param contents the contents of the frame.
- * @throws IOException
- */
- private void writeFrameWithTimeout(final byte[] contents) throws IOException {
- Future<?> future =
- executorService.submit(
- new Runnable() {
- @Override
- public void run() {
- try {
- writeFrame(contents);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
- });
-
- try {
- future.get(IO_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
- } catch (InterruptedException | ExecutionException | TimeoutException e) {
- throw new IOException(e);
- }
- }
-
- /**
- * Reads a frame from the shell's stdout, which has the format:
- *
- * <pre>{@code
- * +---------------------+-----------------+
- * | 4-bytes | |length| bytes |
- * +---------------------+-----------------+
- * | (unsigned) length | contents |
- * +---------------------+-----------------+
- * }</pre>
- *
- * @return the contents that were read
- * @throws IOException
- */
- private byte[] readFrame() throws IOException {
- if (shellProcess == null) {
- throw new IllegalStateException("Shell not started.");
- }
-
- InputStream inputStream = shellProcess.getInputStream();
- byte[] lengthBytes = new byte[4];
- if (inputStream.read(lengthBytes) != lengthBytes.length) {
- throw new IOException("Failed to read length.");
- }
-
- int length = ByteBuffer.wrap(lengthBytes).order(ByteOrder.BIG_ENDIAN).getInt();
- if (length < 0) {
- throw new IOException("Length too large: " + Arrays.toString(lengthBytes));
- }
-
- byte[] contents = new byte[length];
- int bytesRead = inputStream.read(contents);
- if (bytesRead != length) {
- throw new IOException("Failed to read entire contents: " + bytesRead + " != " + length);
- }
-
- return contents;
- }
-
- /**
- * Writes a frame to the shell's stdin, which has the format:
- *
- * <pre>{@code
- * +---------------------+-----------------+
- * | 4-bytes | |length| bytes |
- * +---------------------+-----------------+
- * | (unsigned) length | contents |
- * +---------------------+-----------------+
- * }</pre>
- *
- * @param contents the contents to send.
- * @throws IOException
- */
- private void writeFrame(byte[] contents) throws IOException {
- if (shellProcess == null) {
- throw new IllegalStateException("Shell not started.");
- }
-
- // The length is big-endian encoded, network byte order.
- long length = contents.length;
- byte[] lengthBytes = new byte[4];
- lengthBytes[0] = (byte) (length >> 32 & 0xFF);
- lengthBytes[1] = (byte) (length >> 16 & 0xFF);
- lengthBytes[2] = (byte) (length >> 8 & 0xFF);
- lengthBytes[3] = (byte) (length >> 0 & 0xFF);
-
- OutputStream outputStream = shellProcess.getOutputStream();
- outputStream.write(lengthBytes);
- outputStream.write(contents);
- outputStream.flush();
- }
-
- /**
- * Creates an expression to be processed when a secure connection is established, after the
- * handshake is done.
- *
- * @param command The command to send.
- * @param argument The argument of the command. Can be null.
- * @return the expression that can be sent to the shell.
- * @throws IOException.
- */
- private byte[] createExpression(String command, @Nullable byte[] argument) throws IOException {
- ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
- outputStream.write(command.getBytes());
- outputStream.write(" ".getBytes());
- if (argument != null) {
- outputStream.write(argument);
- }
- return outputStream.toByteArray();
- }
-
- /** @return the mode string to use in the argument to start the ukey2_shell process. */
- private String getModeString() {
- switch (mode) {
- case INITIATOR:
- return "initiator";
- case RESPONDER:
- return "responder";
- default:
- throw new IllegalArgumentException("Uknown mode " + mode);
- }
- }
-}
diff --git a/src/main/javatest/com/google/security/cryptauth/lib/securemessage/CryptoOpsTest.java b/src/main/javatest/com/google/security/cryptauth/lib/securemessage/CryptoOpsTest.java
deleted file mode 100644
index 65fa094..0000000
--- a/src/main/javatest/com/google/security/cryptauth/lib/securemessage/CryptoOpsTest.java
+++ /dev/null
@@ -1,172 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.security.cryptauth.lib.securemessage;
-
-import static org.junit.Assert.assertThrows;
-
-import com.google.security.cryptauth.lib.securemessage.CryptoOps.EncType;
-import com.google.security.cryptauth.lib.securemessage.CryptoOps.SigType;
-import java.util.Arrays;
-import javax.crypto.KeyGenerator;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.SecretKeySpec;
-import junit.framework.TestCase;
-
-/**
- * Unit tests for the CryptoOps class
- */
-public class CryptoOpsTest extends TestCase {
-
- /** HKDF Test Case 1 IKM from RFC 5869 */
- private static final byte[] HKDF_CASE1_IKM = {
- 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
- 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
- 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
- 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
- 0x0b, 0x0b
- };
-
- /** HKDF Test Case 1 salt from RFC 5869 */
- private static final byte[] HKDF_CASE1_SALT = {
- 0x00, 0x01, 0x02, 0x03, 0x04,
- 0x05, 0x06, 0x07, 0x08, 0x09,
- 0x0a, 0x0b, 0x0c
- };
-
- /** HKDF Test Case 1 info from RFC 5869 */
- private static final byte[] HKDF_CASE1_INFO = {
- (byte) 0xf0, (byte) 0xf1, (byte) 0xf2, (byte) 0xf3, (byte) 0xf4,
- (byte) 0xf5, (byte) 0xf6, (byte) 0xf7, (byte) 0xf8, (byte) 0xf9
- };
-
- /** First 32 bytes of HKDF Test Case 1 OKM (output) from RFC 5869 */
- private static final byte[] HKDF_CASE1_OKM = {
- (byte) 0x3c, (byte) 0xb2, (byte) 0x5f, (byte) 0x25, (byte) 0xfa,
- (byte) 0xac, (byte) 0xd5, (byte) 0x7a, (byte) 0x90, (byte) 0x43,
- (byte) 0x4f, (byte) 0x64, (byte) 0xd0, (byte) 0x36, (byte) 0x2f,
- (byte) 0x2a, (byte) 0x2d, (byte) 0x2d, (byte) 0x0a, (byte) 0x90,
- (byte) 0xcf, (byte) 0x1a, (byte) 0x5a, (byte) 0x4c, (byte) 0x5d,
- (byte) 0xb0, (byte) 0x2d, (byte) 0x56, (byte) 0xec, (byte) 0xc4,
- (byte) 0xc5, (byte) 0xbf, (byte) 0x34, (byte) 0x00, (byte) 0x72,
- (byte) 0x08, (byte) 0xd5, (byte) 0xb8, (byte) 0x87, (byte) 0x18,
- (byte) 0x58, (byte) 0x65
- };
-
- private SecretKey aesKey1;
- private SecretKey aesKey2;
-
- @Override
- protected void setUp() throws Exception {
- KeyGenerator aesKeygen = KeyGenerator.getInstance("AES");
- aesKeygen.init(256);
- aesKey1 = aesKeygen.generateKey();
- aesKey2 = aesKeygen.generateKey();
- super.setUp();
- }
-
- public void testNoPurposeConflicts() {
- // Ensure that signature algorithms and encryption algorithms are not given identical purposes
- // (this prevents confusion of derived keys).
- for (SigType sigType : SigType.values()) {
- for (EncType encType : EncType.values()) {
- assertFalse(CryptoOps.getPurpose(sigType).equals(CryptoOps.getPurpose(encType)));
- }
- }
- }
-
- public void testDeriveAes256KeyFor() throws Exception {
- // Test that deriving with the same key and purpose twice is deterministic
- assertTrue(Arrays.equals(CryptoOps.deriveAes256KeyFor(aesKey1, "A").getEncoded(),
- CryptoOps.deriveAes256KeyFor(aesKey1, "A").getEncoded()));
- // Test that derived keys with different purposes differ
- assertFalse(Arrays.equals(CryptoOps.deriveAes256KeyFor(aesKey1, "A").getEncoded(),
- CryptoOps.deriveAes256KeyFor(aesKey1, "B").getEncoded()));
- // Test that derived keys with the same purpose but different master keys differ
- assertFalse(Arrays.equals(CryptoOps.deriveAes256KeyFor(aesKey1, "A").getEncoded(),
- CryptoOps.deriveAes256KeyFor(aesKey2, "A").getEncoded()));
- }
-
- public void testHkdf() throws Exception {
- SecretKey inputKey = new SecretKeySpec(HKDF_CASE1_IKM, "AES");
- byte[] result = CryptoOps.hkdf(inputKey, HKDF_CASE1_SALT, HKDF_CASE1_INFO);
- byte[] expectedResult = Arrays.copyOf(HKDF_CASE1_OKM, 32);
- assertTrue(Arrays.equals(result, expectedResult));
- }
-
- public void testHkdfLongOutput() throws Exception {
- SecretKey inputKey = new SecretKeySpec(HKDF_CASE1_IKM, "AES");
- byte[] result = CryptoOps.hkdf(inputKey, HKDF_CASE1_SALT, HKDF_CASE1_INFO, 42);
- byte[] expectedResult = Arrays.copyOf(HKDF_CASE1_OKM, 42);
- assertTrue(Arrays.equals(result, expectedResult));
- }
-
- public void testHkdfShortOutput() throws Exception {
- SecretKey inputKey = new SecretKeySpec(HKDF_CASE1_IKM, "AES");
- byte[] result = CryptoOps.hkdf(inputKey, HKDF_CASE1_SALT, HKDF_CASE1_INFO, 12);
- byte[] expectedResult = Arrays.copyOf(HKDF_CASE1_OKM, 12);
- assertTrue(Arrays.equals(result, expectedResult));
- }
-
- public void testHkdfInvalidLengths() throws Exception {
- SecretKey inputKey = new SecretKeySpec(HKDF_CASE1_IKM, "AES");
-
- // Negative length
- assertThrows(
- IllegalArgumentException.class,
- () -> CryptoOps.hkdf(inputKey, HKDF_CASE1_SALT, HKDF_CASE1_INFO, -5));
-
- // Too long, would be more than 256 blocks
- assertThrows(
- IllegalArgumentException.class,
- () -> CryptoOps.hkdf(inputKey, HKDF_CASE1_SALT, HKDF_CASE1_INFO, 32 * 256 + 1));
- }
-
- public void testConcat() {
- byte[] a = { 1, 2, 3, 4};
- byte[] b = { 5 , 6 };
- byte[] expectedResult = { 1, 2, 3, 4, 5, 6 };
- byte[] result = CryptoOps.concat(a, b);
- assertEquals(a.length + b.length, result.length);
- assertTrue(Arrays.equals(expectedResult, result));
-
- byte[] empty = { };
- assertEquals(0, CryptoOps.concat(empty, empty).length);
- assertTrue(Arrays.equals(a, CryptoOps.concat(a, empty)));
- assertTrue(Arrays.equals(a, CryptoOps.concat(empty, a)));
-
- assertEquals(0, CryptoOps.concat(null, null).length);
- assertTrue(Arrays.equals(a, CryptoOps.concat(a, null)));
- assertTrue(Arrays.equals(a, CryptoOps.concat(null, a)));
- }
-
- public void testSubarray() {
- byte[] in = { 1, 2, 3, 4, 5, 6, 7 };
- assertTrue(Arrays.equals(in, CryptoOps.subarray(in, 0, in.length)));
- assertEquals(0, CryptoOps.subarray(in, 0, 0).length);
- byte[] expectedResult1 = { 1 };
- assertTrue(Arrays.equals(expectedResult1, CryptoOps.subarray(in, 0, 1)));
- byte[] expectedResult34 = { 3, 4 };
- assertTrue(Arrays.equals(expectedResult34, CryptoOps.subarray(in, 2, 4)));
- assertThrows(IndexOutOfBoundsException.class, () -> CryptoOps.subarray(in, 0, in.length + 1));
- assertThrows(IndexOutOfBoundsException.class, () -> CryptoOps.subarray(in, -1, in.length));
- assertThrows(
- IndexOutOfBoundsException.class, () -> CryptoOps.subarray(in, in.length, in.length));
- assertThrows(
- IndexOutOfBoundsException.class,
- () -> CryptoOps.subarray(in, Integer.MIN_VALUE, in.length));
- assertThrows(
- IndexOutOfBoundsException.class, () -> CryptoOps.subarray(in, 1, Integer.MIN_VALUE));
- }
-}
diff --git a/src/main/javatest/com/google/security/cryptauth/lib/securemessage/NullsGoogle3Test.java b/src/main/javatest/com/google/security/cryptauth/lib/securemessage/NullsGoogle3Test.java
deleted file mode 100644
index c28d2f9..0000000
--- a/src/main/javatest/com/google/security/cryptauth/lib/securemessage/NullsGoogle3Test.java
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.security.cryptauth.lib.securemessage;
-
-import com.google.common.testing.NullPointerTester;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.SecureMessage;
-import junit.framework.TestCase;
-
-/**
- * Non-portable Google3-based test to check null pointer behavior.
- */
-public class NullsGoogle3Test extends TestCase {
-
- /**
- * We test all of the classes in one place to avoid a proliferation of similar test cases,
- * noting that {@link NullPointerTester} emits the name of the class where the breakge occurs.
- */
- public void testNulls() {
- final NullPointerTester tester = new NullPointerTester();
- tester.testAllPublicStaticMethods(CryptoOps.class);
- tester.testAllPublicStaticMethods(PublicKeyProtoUtil.class);
-
- tester.setDefault(SecureMessage.class, SecureMessage.getDefaultInstance());
- tester.testAllPublicStaticMethods(SecureMessageParser.class);
-
- tester.testAllPublicStaticMethods(SecureMessageBuilder.class);
- tester.testAllPublicConstructors(SecureMessageBuilder.class);
- tester.testAllPublicInstanceMethods(new SecureMessageBuilder());
- }
-}
diff --git a/src/main/javatest/com/google/security/cryptauth/lib/securemessage/PublicKeyProtoUtilTest.java b/src/main/javatest/com/google/security/cryptauth/lib/securemessage/PublicKeyProtoUtilTest.java
deleted file mode 100644
index 8581622..0000000
--- a/src/main/javatest/com/google/security/cryptauth/lib/securemessage/PublicKeyProtoUtilTest.java
+++ /dev/null
@@ -1,412 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.security.cryptauth.lib.securemessage;
-
-import com.google.common.io.BaseEncoding;
-import com.google.protobuf.ByteString;
-import com.google.security.annotations.SuppressInsecureCipherModeCheckerNoReview;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.DhPublicKey;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.EcP256PublicKey;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.GenericPublicKey;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.SimpleRsaPublicKey;
-import java.math.BigInteger;
-import java.security.KeyFactory;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.NoSuchAlgorithmException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.interfaces.ECPublicKey;
-import java.security.spec.ECPoint;
-import java.security.spec.ECPublicKeySpec;
-import java.security.spec.InvalidKeySpecException;
-import java.util.Arrays;
-import javax.crypto.KeyAgreement;
-import javax.crypto.interfaces.DHPrivateKey;
-import javax.crypto.interfaces.DHPublicKey;
-import junit.framework.TestCase;
-
-/** Tests for the PublicKeyProtoUtil class. */
-public class PublicKeyProtoUtilTest extends TestCase {
-
- private static final byte[] ZERO_BYTE = {0};
- private PublicKey ecPublicKey;
- private PublicKey rsaPublicKey;
-
- /**
- * Diffie Hellman {@link PublicKey}s require special treatment, so we store them specifically as a
- * {@link DHPublicKey} to minimize casting.
- */
- private DHPublicKey dhPublicKey;
-
- @Override
- public void setUp() {
- if (!isAndroidOsWithoutEcSupport()) {
- ecPublicKey = PublicKeyProtoUtil.generateEcP256KeyPair().getPublic();
- }
- rsaPublicKey = PublicKeyProtoUtil.generateRSA2048KeyPair().getPublic();
- dhPublicKey = (DHPublicKey) PublicKeyProtoUtil.generateDh2048KeyPair().getPublic();
- }
-
- public void testPublicKeyProtoSpecificEncodeParse() throws Exception {
- if (!isAndroidOsWithoutEcSupport()) {
- assertEquals(
- ecPublicKey,
- PublicKeyProtoUtil.parseEcPublicKey(PublicKeyProtoUtil.encodeEcPublicKey(ecPublicKey)));
- }
-
- assertEquals(
- rsaPublicKey,
- PublicKeyProtoUtil.parseRsa2048PublicKey(
- PublicKeyProtoUtil.encodeRsa2048PublicKey(rsaPublicKey)));
-
- // DHPublicKey objects don't seem to properly implement equals(), so we have to test that
- // the individual y and p values match (it is safe to assume g = 2 is used if p is correct).
- DHPublicKey parsedDHPublicKey =
- PublicKeyProtoUtil.parseDh2048PublicKey(
- PublicKeyProtoUtil.encodeDh2048PublicKey(dhPublicKey));
- assertEquals(dhPublicKey.getY(), parsedDHPublicKey.getY());
- assertEquals(dhPublicKey.getParams().getP(), parsedDHPublicKey.getParams().getP());
- assertEquals(dhPublicKey.getParams().getG(), parsedDHPublicKey.getParams().getG());
- }
-
- public void testPublicKeyProtoGenericEncodeParse() throws Exception {
- if (!isAndroidOsWithoutEcSupport()) {
- assertEquals(
- ecPublicKey,
- PublicKeyProtoUtil.parsePublicKey(
- PublicKeyProtoUtil.encodePaddedEcPublicKey(ecPublicKey)));
- assertEquals(
- ecPublicKey,
- PublicKeyProtoUtil.parsePublicKey(PublicKeyProtoUtil.encodePublicKey(ecPublicKey)));
- }
-
- assertEquals(
- rsaPublicKey,
- PublicKeyProtoUtil.parsePublicKey(PublicKeyProtoUtil.encodePublicKey(rsaPublicKey)));
-
- // See above explanation for why we treat DHPublicKey objects differently.
- DHPublicKey parsedDHPublicKey =
- PublicKeyProtoUtil.parseDh2048PublicKey(
- PublicKeyProtoUtil.encodeDh2048PublicKey(dhPublicKey));
- assertEquals(dhPublicKey.getY(), parsedDHPublicKey.getY());
- assertEquals(dhPublicKey.getParams().getP(), parsedDHPublicKey.getParams().getP());
- assertEquals(dhPublicKey.getParams().getG(), parsedDHPublicKey.getParams().getG());
- }
-
- public void testPaddedECPublicKeyEncodeHasPaddedNullByte() throws Exception {
- if (isAndroidOsWithoutEcSupport()) {
- return;
- }
-
- // Key where the x coordinate is 33 bytes, y coordinate is 32 bytes
- ECPublicKey maxXByteLengthKey =
- buildEcPublicKey(
- BaseEncoding.base64().decode("AM730WQL7ZAmvyAJX4euNdr3+nAIueGlYYGXE6p732h6"),
- BaseEncoding.base64().decode("JEnmaDpKn0fH4/0kKGb97qUSwI2uT+ta0GLe3V7REfk="));
- // Key where both coordinates are 33 bytes
- ECPublicKey maxByteLengthKey =
- buildEcPublicKey(
- BaseEncoding.base64().decode("AOg9TQCxFfVdXv7lO/6UVDyiPsu8XDkEWQIPUfqX6UHP"),
- BaseEncoding.base64().decode("AP/RW8uVyu6QImpbza51CqG1mtBTh5c9pjv9CUwOuB7E"));
- // Key where both coordinates are 32 bytes
- ECPublicKey notMaxByteLengthKey =
- buildEcPublicKey(
- BaseEncoding.base64().decode("M35bxV8HKr0e8v7f4zuXgw6TYFawvikFdI71u9S1ONI="),
- BaseEncoding.base64().decode("OXR+xCpD8AR0VR8TeBXA00eIr3rWE6sV6KrOM6MoWsc="));
- GenericPublicKey encodedMaxXByteLengthKey =
- PublicKeyProtoUtil.encodePublicKey(maxXByteLengthKey);
- GenericPublicKey paddedEncodedMaxXByteLengthKey =
- PublicKeyProtoUtil.encodePaddedEcPublicKey(maxXByteLengthKey);
- GenericPublicKey encodedMaxByteLengthKey = PublicKeyProtoUtil.encodePublicKey(maxByteLengthKey);
- GenericPublicKey paddedEncodedMaxByteLengthKey =
- PublicKeyProtoUtil.encodePaddedEcPublicKey(maxByteLengthKey);
- GenericPublicKey encodedNotMaxByteLengthKey =
- PublicKeyProtoUtil.encodePublicKey(notMaxByteLengthKey);
- GenericPublicKey paddedEncodedNotMaxByteLengthKey =
- PublicKeyProtoUtil.encodePaddedEcPublicKey(notMaxByteLengthKey);
-
- assertEquals(maxXByteLengthKey, PublicKeyProtoUtil.parsePublicKey(encodedMaxXByteLengthKey));
- assertEquals(
- maxXByteLengthKey, PublicKeyProtoUtil.parsePublicKey(paddedEncodedMaxXByteLengthKey));
- assertEquals(maxByteLengthKey, PublicKeyProtoUtil.parsePublicKey(encodedMaxByteLengthKey));
- assertEquals(
- maxByteLengthKey, PublicKeyProtoUtil.parsePublicKey(paddedEncodedMaxByteLengthKey));
- assertEquals(
- notMaxByteLengthKey, PublicKeyProtoUtil.parsePublicKey(paddedEncodedNotMaxByteLengthKey));
- assertEquals(
- notMaxByteLengthKey, PublicKeyProtoUtil.parsePublicKey(encodedNotMaxByteLengthKey));
-
- assertEquals(33, paddedEncodedMaxXByteLengthKey.getEcP256PublicKey().getX().size());
- assertEquals(33, paddedEncodedMaxXByteLengthKey.getEcP256PublicKey().getY().size());
- assertEquals(0, paddedEncodedMaxXByteLengthKey.getEcP256PublicKey().getX().byteAt(0));
- assertEquals(0, paddedEncodedMaxXByteLengthKey.getEcP256PublicKey().getY().byteAt(0));
- assertEquals(33, encodedMaxXByteLengthKey.getEcP256PublicKey().getX().size());
- assertEquals(32, encodedMaxXByteLengthKey.getEcP256PublicKey().getY().size());
-
- assertEquals(33, paddedEncodedMaxByteLengthKey.getEcP256PublicKey().getX().size());
- assertEquals(33, paddedEncodedMaxByteLengthKey.getEcP256PublicKey().getY().size());
- assertEquals(0, paddedEncodedMaxByteLengthKey.getEcP256PublicKey().getX().byteAt(0));
- assertEquals(0, paddedEncodedMaxByteLengthKey.getEcP256PublicKey().getY().byteAt(0));
- assertEquals(33, encodedMaxByteLengthKey.getEcP256PublicKey().getX().size());
- assertEquals(33, encodedMaxByteLengthKey.getEcP256PublicKey().getY().size());
-
- assertEquals(32, encodedNotMaxByteLengthKey.getEcP256PublicKey().getX().size());
- assertEquals(32, encodedNotMaxByteLengthKey.getEcP256PublicKey().getY().size());
- assertEquals(0, paddedEncodedNotMaxByteLengthKey.getEcP256PublicKey().getX().byteAt(0));
- assertEquals(0, paddedEncodedNotMaxByteLengthKey.getEcP256PublicKey().getY().byteAt(0));
- assertEquals(33, paddedEncodedNotMaxByteLengthKey.getEcP256PublicKey().getX().size());
- assertEquals(33, paddedEncodedNotMaxByteLengthKey.getEcP256PublicKey().getY().size());
- }
-
- @SuppressInsecureCipherModeCheckerNoReview
- public void testWrongPublicKeyType() throws Exception {
- KeyPairGenerator dsaGen = KeyPairGenerator.getInstance("DSA");
- dsaGen.initialize(512);
- PublicKey pk = dsaGen.generateKeyPair().getPublic();
-
- if (!isAndroidOsWithoutEcSupport()) {
- // Try to encode it as EC
- try {
- PublicKeyProtoUtil.encodeEcPublicKey(pk);
- fail();
- } catch (IllegalArgumentException expected) {
- }
-
- try {
- PublicKeyProtoUtil.encodePaddedEcPublicKey(pk);
- fail();
- } catch (IllegalArgumentException expected) {
- }
- }
-
- // Try to encode it as RSA
- try {
- PublicKeyProtoUtil.encodeRsa2048PublicKey(pk);
- fail();
- } catch (IllegalArgumentException expected) {
- }
-
- // Try to encode it as DH
- try {
- PublicKeyProtoUtil.encodeDh2048PublicKey(pk);
- fail();
- } catch (IllegalArgumentException expected) {
- }
-
- // Try to encode it as Generic
- try {
- PublicKeyProtoUtil.encodePublicKey(pk);
- fail();
- } catch (IllegalArgumentException expected) {
- }
- }
-
- public void testEcPublicKeyProtoInvalidEncoding() throws Exception {
- if (isAndroidOsWithoutEcSupport()) {
- return;
- }
-
- EcP256PublicKey validProto = PublicKeyProtoUtil.encodeEcPublicKey(ecPublicKey);
- EcP256PublicKey.Builder invalidProto = EcP256PublicKey.newBuilder(validProto);
-
- // Mess up the X coordinate by repeating it twice
- byte[] newX =
- CryptoOps.concat(validProto.getX().toByteArray(), validProto.getX().toByteArray());
- checkParsingFailsFor(invalidProto.setX(ByteString.copyFrom(newX)).build());
-
- // Mess up the Y coordinate by erasing it
- invalidProto = EcP256PublicKey.newBuilder(validProto);
- checkParsingFailsFor(invalidProto.setY(ByteString.EMPTY).build());
-
- // Pick a point that is likely not on the curve by copying X over Y
- invalidProto = EcP256PublicKey.newBuilder(validProto);
- checkParsingFailsFor(invalidProto.setY(validProto.getX()).build());
-
- // Try the point (0, 0)
- invalidProto = EcP256PublicKey.newBuilder(validProto);
- checkParsingFailsFor(
- invalidProto
- .setX(ByteString.copyFrom(ZERO_BYTE))
- .setY(ByteString.copyFrom(ZERO_BYTE))
- .build());
- }
-
- private void checkParsingFailsFor(EcP256PublicKey invalid) {
- try {
- // Should fail to decode
- PublicKeyProtoUtil.parseEcPublicKey(invalid);
- fail();
- } catch (InvalidKeySpecException expected) {
- }
- }
-
- public void testSimpleRsaPublicKeyProtoInvalidEncoding() throws Exception {
- SimpleRsaPublicKey validProto = PublicKeyProtoUtil.encodeRsa2048PublicKey(rsaPublicKey);
- SimpleRsaPublicKey.Builder invalidProto;
-
- // Double the number of bits in the modulus
- invalidProto = SimpleRsaPublicKey.newBuilder(validProto);
- byte[] newN =
- CryptoOps.concat(validProto.getN().toByteArray(), validProto.getN().toByteArray());
- checkParsingFailsFor(invalidProto.setN(ByteString.copyFrom(newN)).build());
-
- // Set the modulus to 0
- invalidProto = SimpleRsaPublicKey.newBuilder(validProto);
- checkParsingFailsFor(invalidProto.setN(ByteString.copyFrom(ZERO_BYTE)).build());
-
- // Set the modulus to 65537 (way too small)
- invalidProto = SimpleRsaPublicKey.newBuilder(validProto);
- checkParsingFailsFor(
- invalidProto.setN(ByteString.copyFrom(BigInteger.valueOf(65537).toByteArray())).build());
- }
-
- private static void checkParsingFailsFor(SimpleRsaPublicKey invalid) {
- try {
- // Should fail to decode
- PublicKeyProtoUtil.parseRsa2048PublicKey(invalid);
- fail();
- } catch (InvalidKeySpecException expected) {
- }
- }
-
- public void testSimpleDhPublicKeyProtoInvalidEncoding() throws Exception {
- DhPublicKey validProto = PublicKeyProtoUtil.encodeDh2048PublicKey(dhPublicKey);
- DhPublicKey.Builder invalidProto;
-
- // Double the number of bits in the public element encoding
- invalidProto = DhPublicKey.newBuilder(validProto);
- byte[] newY =
- CryptoOps.concat(validProto.getY().toByteArray(), validProto.getY().toByteArray());
- checkParsingFailsFor(invalidProto.setY(ByteString.copyFrom(newY)).build());
-
- // Set the public element to 0
- invalidProto = DhPublicKey.newBuilder(validProto);
- checkParsingFailsFor(invalidProto.setY(ByteString.copyFrom(ZERO_BYTE)).build());
- }
-
- private static void checkParsingFailsFor(DhPublicKey invalid) {
- try {
- // Should fail to decode
- PublicKeyProtoUtil.parseDh2048PublicKey(invalid);
- fail();
- } catch (InvalidKeySpecException expected) {
- }
- }
-
- public void testDhKeyAgreementWorks() throws Exception {
- int minExpectedSecretLength = (PublicKeyProtoUtil.DH_P.bitLength() / 8) - 4;
-
- KeyPair clientKeyPair = PublicKeyProtoUtil.generateDh2048KeyPair();
- KeyPair serverKeyPair = PublicKeyProtoUtil.generateDh2048KeyPair();
- BigInteger clientY = ((DHPublicKey) clientKeyPair.getPublic()).getY();
- BigInteger serverY = ((DHPublicKey) serverKeyPair.getPublic()).getY();
- assertFalse(clientY.equals(serverY)); // DHPublicKeys should not be equal
-
- // Run client side of the key exchange
- byte[] clientSecret = doDhAgreement(clientKeyPair.getPrivate(), serverKeyPair.getPublic());
- assert (clientSecret.length >= minExpectedSecretLength);
-
- // Run the server side of the key exchange
- byte[] serverSecret = doDhAgreement(serverKeyPair.getPrivate(), clientKeyPair.getPublic());
- assert (serverSecret.length >= minExpectedSecretLength);
-
- assertTrue(Arrays.equals(clientSecret, serverSecret));
- }
-
- public void testDh2048PrivateKeyEncoding() throws Exception {
- KeyPair testPair = PublicKeyProtoUtil.generateDh2048KeyPair();
- DHPrivateKey sk = (DHPrivateKey) testPair.getPrivate();
- DHPrivateKey skParsed =
- PublicKeyProtoUtil.parseDh2048PrivateKey(PublicKeyProtoUtil.encodeDh2048PrivateKey(sk));
- assertEquals(sk.getX(), skParsed.getX());
- assertEquals(sk.getParams().getP(), skParsed.getParams().getP());
- assertEquals(sk.getParams().getG(), skParsed.getParams().getG());
- }
-
- public void testParseEcPublicKeyOnLegacyPlatform() {
- if (!PublicKeyProtoUtil.isLegacyCryptoRequired()) {
- return; // This test only runs on legacy platforms
- }
- byte[] pointBytes = {
- 1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
- 1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
- 1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
- 1, 2
- };
-
- try {
- PublicKeyProtoUtil.parseEcPublicKey(
- EcP256PublicKey.newBuilder()
- .setX(ByteString.copyFrom(pointBytes))
- .setY(ByteString.copyFrom(pointBytes))
- .build());
- fail();
- } catch (InvalidKeySpecException expected) {
- // Should get this specific exception when EC doesn't work
- }
- }
-
- public void testIsLegacyCryptoRequired() {
- assertEquals(isAndroidOsWithoutEcSupport(), PublicKeyProtoUtil.isLegacyCryptoRequired());
- }
-
- /** @return true if running on an Android OS that doesn't support Elliptic Curve algorithms */
- public static boolean isAndroidOsWithoutEcSupport() {
- try {
- Class<?> clazz = ClassLoader.getSystemClassLoader().loadClass("android.os.Build$VERSION");
- int sdkVersion = clazz.getField("SDK_INT").getInt(null);
- if (sdkVersion < PublicKeyProtoUtil.ANDROID_HONEYCOMB_SDK_INT) {
- return true;
- }
- } catch (ClassNotFoundException e) {
- // Not running on Android
- return false;
- } catch (SecurityException e) {
- throw new AssertionError(e);
- } catch (NoSuchFieldException e) {
- throw new AssertionError(e);
- } catch (IllegalArgumentException e) {
- throw new AssertionError(e);
- } catch (IllegalAccessException e) {
- throw new AssertionError(e);
- }
- return false;
- }
-
- @SuppressInsecureCipherModeCheckerNoReview
- private static byte[] doDhAgreement(PrivateKey secretKey, PublicKey peerKey) throws Exception {
- KeyAgreement agreement = KeyAgreement.getInstance("DH");
- agreement.init(secretKey);
- agreement.doPhase(peerKey, true);
- return agreement.generateSecret();
- }
-
- private static ECPublicKey buildEcPublicKey(byte[] encodedX, byte[] encodedY) throws Exception {
- try {
- BigInteger wX = new BigInteger(encodedX);
- BigInteger wY = new BigInteger(encodedY);
- return (ECPublicKey)
- KeyFactory.getInstance("EC")
- .generatePublic(
- new ECPublicKeySpec(
- new ECPoint(wX, wY),
- ((ECPublicKey) PublicKeyProtoUtil.generateEcP256KeyPair().getPublic())
- .getParams()));
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException(e);
- }
- }
-}
diff --git a/src/main/javatest/com/google/security/cryptauth/lib/securemessage/SecureMessageSimpleTestVectorTest.java b/src/main/javatest/com/google/security/cryptauth/lib/securemessage/SecureMessageSimpleTestVectorTest.java
deleted file mode 100644
index 285b259..0000000
--- a/src/main/javatest/com/google/security/cryptauth/lib/securemessage/SecureMessageSimpleTestVectorTest.java
+++ /dev/null
@@ -1,403 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.security.cryptauth.lib.securemessage;
-
-import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.security.cryptauth.lib.securemessage.CryptoOps.EncType;
-import com.google.security.cryptauth.lib.securemessage.CryptoOps.SigType;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.GenericPublicKey;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.Header;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.HeaderAndBody;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.SecureMessage;
-import java.security.KeyFactory;
-import java.security.KeyPair;
-import java.security.NoSuchAlgorithmException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.spec.InvalidKeySpecException;
-import java.security.spec.PKCS8EncodedKeySpec;
-import java.util.Arrays;
-import javax.crypto.KeyGenerator;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.SecretKeySpec;
-import junit.framework.TestCase;
-
-/**
- * Tests the library against some very basic test vectors, to help ensure wire-format
- * compatibility is not broken.
- */
-public class SecureMessageSimpleTestVectorTest extends TestCase {
-
- private static final KeyFactory EC_KEY_FACTORY;
- static {
- try {
- if (PublicKeyProtoUtil.isLegacyCryptoRequired()) {
- EC_KEY_FACTORY = null;
- } else {
- EC_KEY_FACTORY = KeyFactory.getInstance("EC");
- }
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
- private static final byte[] TEST_ASSOCIATED_DATA = {
- 11, 22, 33, 44, 55
- };
- private static final byte[] TEST_METADATA = {
- 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28
- };
- private static final byte[] TEST_VKID = {
- 0, 0, 1
- };
- private static final byte[] TEST_DKID = {
- -1, -1, 0,
- };
- private static final byte[] TEST_MESSAGE = {
- 0, 99, 1, 98, 2, 97, 3, 96, 4, 95, 5, 94, 6, 93, 7, 92, 8, 91, 9, 90
- };
-
- // The following fields are initialized below, in a static block that contains auto-generated test
- // vectors. Initialization can't just be done inline due to code that throws checked exceptions.
- private static final PublicKey TEST_EC_PUBLIC_KEY;
- private static final PrivateKey TEST_EC_PRIVATE_KEY;
- private static final SecretKey TEST_KEY1;
- private static final SecretKey TEST_KEY2;
- private static final byte[] TEST_VECTOR_ECDSA_ONLY;
- private static final byte[] TEST_VECTOR_ECDSA_AND_AES;
- private static final byte[] TEST_VECTOR_HMAC_AND_AES_SAME_KEYS;
- private static final byte[] TEST_VECTOR_HMAC_AND_AES_DIFFERENT_KEYS;
-
- public void testEcdsaOnly() throws Exception {
- if (PublicKeyProtoUtil.isLegacyCryptoRequired()) {
- // On older Android platforms we can't run this test.
- return;
- }
- SecureMessage testVector = SecureMessage.parseFrom(TEST_VECTOR_ECDSA_ONLY);
- Header unverifiedHeader = SecureMessageParser.getUnverifiedHeader(testVector);
- HeaderAndBody headerAndBody = SecureMessageParser.parseSignedCleartextMessage(
- testVector, TEST_EC_PUBLIC_KEY, SigType.ECDSA_P256_SHA256, TEST_ASSOCIATED_DATA);
- assertTrue(Arrays.equals(
- unverifiedHeader.toByteArray(),
- headerAndBody.getHeader().toByteArray()));
- assertTrue(Arrays.equals(TEST_MESSAGE, headerAndBody.getBody().toByteArray()));
- assertEquals(TEST_ASSOCIATED_DATA.length, unverifiedHeader.getAssociatedDataLength());
- assertTrue(Arrays.equals(TEST_METADATA, unverifiedHeader.getPublicMetadata().toByteArray()));
- assertTrue(Arrays.equals(TEST_VKID, unverifiedHeader.getVerificationKeyId().toByteArray()));
- assertFalse(unverifiedHeader.hasDecryptionKeyId());
- }
-
- public void testEcdsaAndAes() throws Exception {
- if (PublicKeyProtoUtil.isLegacyCryptoRequired()) {
- // On older Android platforms we can't run this test.
- return;
- }
- SecureMessage testVector = SecureMessage.parseFrom(TEST_VECTOR_ECDSA_AND_AES);
- Header unverifiedHeader = SecureMessageParser.getUnverifiedHeader(testVector);
- HeaderAndBody headerAndBody = SecureMessageParser.parseSignCryptedMessage(
- testVector,
- TEST_EC_PUBLIC_KEY,
- SigType.ECDSA_P256_SHA256,
- TEST_KEY1,
- EncType.AES_256_CBC,
- TEST_ASSOCIATED_DATA);
- assertTrue(Arrays.equals(
- unverifiedHeader.toByteArray(),
- headerAndBody.getHeader().toByteArray()));
- assertTrue(Arrays.equals(TEST_MESSAGE, headerAndBody.getBody().toByteArray()));
- assertEquals(TEST_ASSOCIATED_DATA.length, unverifiedHeader.getAssociatedDataLength());
- assertTrue(Arrays.equals(TEST_METADATA, unverifiedHeader.getPublicMetadata().toByteArray()));
- assertTrue(Arrays.equals(TEST_VKID, unverifiedHeader.getVerificationKeyId().toByteArray()));
- assertTrue(Arrays.equals(TEST_DKID, unverifiedHeader.getDecryptionKeyId().toByteArray()));
- }
-
- public void testHmacAndAesSameKeys() throws Exception {
- SecureMessage testVector = SecureMessage.parseFrom(TEST_VECTOR_HMAC_AND_AES_SAME_KEYS);
- Header unverifiedHeader = SecureMessageParser.getUnverifiedHeader(testVector);
-
- HeaderAndBody headerAndBody = SecureMessageParser.parseSignCryptedMessage(
- testVector,
- TEST_KEY1,
- SigType.HMAC_SHA256,
- TEST_KEY1,
- EncType.AES_256_CBC,
- TEST_ASSOCIATED_DATA);
- assertTrue(Arrays.equals(
- unverifiedHeader.toByteArray(),
- headerAndBody.getHeader().toByteArray()));
- assertTrue(Arrays.equals(TEST_MESSAGE, headerAndBody.getBody().toByteArray()));
- assertEquals(TEST_ASSOCIATED_DATA.length, unverifiedHeader.getAssociatedDataLength());
- assertTrue(Arrays.equals(TEST_METADATA, unverifiedHeader.getPublicMetadata().toByteArray()));
- assertTrue(Arrays.equals(TEST_VKID, unverifiedHeader.getVerificationKeyId().toByteArray()));
- assertTrue(Arrays.equals(TEST_DKID, unverifiedHeader.getDecryptionKeyId().toByteArray()));
- }
-
- public void testHmacAndAesDifferentKeys() throws Exception {
- SecureMessage testVector = SecureMessage.parseFrom(TEST_VECTOR_HMAC_AND_AES_DIFFERENT_KEYS);
- Header unverifiedHeader = SecureMessageParser.getUnverifiedHeader(testVector);
- HeaderAndBody headerAndBody = SecureMessageParser.parseSignCryptedMessage(
- testVector,
- TEST_KEY1,
- SigType.HMAC_SHA256,
- TEST_KEY2,
- EncType.AES_256_CBC,
- TEST_ASSOCIATED_DATA);
- assertTrue(Arrays.equals(
- unverifiedHeader.toByteArray(),
- headerAndBody.getHeader().toByteArray()));
- assertTrue(Arrays.equals(TEST_MESSAGE, headerAndBody.getBody().toByteArray()));
- assertEquals(TEST_ASSOCIATED_DATA.length, unverifiedHeader.getAssociatedDataLength());
- assertTrue(Arrays.equals(TEST_METADATA, unverifiedHeader.getPublicMetadata().toByteArray()));
- assertTrue(Arrays.equals(TEST_VKID, unverifiedHeader.getVerificationKeyId().toByteArray()));
- assertTrue(Arrays.equals(TEST_DKID, unverifiedHeader.getDecryptionKeyId().toByteArray()));
- }
-
- /**
- * This code emits the test vectors to {@code System.out}. It will not generate fresh test
- * vectors unless an existing test vector is set to {@code null}, but it contains all of the code
- * used to the generate the test vector values. Ideally, existing test vectors should never be
- * regenerated, but having this code available should make it easier to add new test vectors.
- */
- public void testGenerateTestVectorsPseudoTest() throws Exception {
- if (PublicKeyProtoUtil.isLegacyCryptoRequired()) {
- // On older Android platforms we can't run this test.
- return;
- }
- System.out.printf(" static {\n try {\n");
- String indent = " ";
- PublicKey testEcPublicKey = TEST_EC_PUBLIC_KEY;
- PrivateKey testEcPrivateKey = TEST_EC_PRIVATE_KEY;
- if (testEcPublicKey == null) {
- KeyPair testEcKeyPair = PublicKeyProtoUtil.generateEcP256KeyPair();
- testEcPublicKey = testEcKeyPair.getPublic();
- testEcPrivateKey = testEcKeyPair.getPrivate();
- }
- System.out.printf("%s%s = parsePublicKey(new byte[] %s);\n",
- indent,
- "TEST_EC_PUBLIC_KEY",
- byteArrayToJavaCode(indent, encodePublicKey(testEcPublicKey)));
- System.out.printf("%s%s = parseEcPrivateKey(new byte[] %s);\n",
- indent,
- "TEST_EC_PRIVATE_KEY",
- byteArrayToJavaCode(indent, encodeEcPrivateKey(testEcPrivateKey)));
-
- SecretKey testKey1 = TEST_KEY1;
- if (testKey1 == null) {
- testKey1 = makeAesKey();
- }
- System.out.printf("%s%s = new SecretKeySpec(new byte[] %s, \"AES\");\n",
- indent,
- "TEST_KEY1",
- byteArrayToJavaCode(indent, testKey1.getEncoded()));
-
- SecretKey testKey2 = TEST_KEY2;
- if (testKey2 == null) {
- testKey2 = makeAesKey();
- }
- System.out.printf("%s%s = new SecretKeySpec(new byte[] %s, \"AES\");\n",
- indent,
- "TEST_KEY2",
- byteArrayToJavaCode(indent, testKey2.getEncoded()));
-
- byte[] testVectorEcdsaOnly = TEST_VECTOR_ECDSA_ONLY;
- if (testVectorEcdsaOnly == null) {
- testVectorEcdsaOnly = new SecureMessageBuilder()
- .setAssociatedData(TEST_ASSOCIATED_DATA)
- .setPublicMetadata(TEST_METADATA)
- .setVerificationKeyId(TEST_VKID)
- .buildSignedCleartextMessage(
- testEcPrivateKey, SigType.ECDSA_P256_SHA256, TEST_MESSAGE).toByteArray();
- }
- printInitializerFor(indent, "TEST_VECTOR_ECDSA_ONLY", testVectorEcdsaOnly);
-
- byte[] testVectorEcdsaAndAes = TEST_VECTOR_ECDSA_AND_AES;
- if (testVectorEcdsaAndAes == null) {
- testVectorEcdsaAndAes = new SecureMessageBuilder()
- .setAssociatedData(TEST_ASSOCIATED_DATA)
- .setDecryptionKeyId(TEST_DKID)
- .setPublicMetadata(TEST_METADATA)
- .setVerificationKeyId(TEST_VKID)
- .buildSignCryptedMessage(
- testEcPrivateKey,
- SigType.ECDSA_P256_SHA256,
- testKey1,
- EncType.AES_256_CBC,
- TEST_MESSAGE).toByteArray();
- }
- printInitializerFor(indent, "TEST_VECTOR_ECDSA_AND_AES", testVectorEcdsaAndAes);
-
- byte[] testVectorHmacAndAesSameKeys = TEST_VECTOR_HMAC_AND_AES_SAME_KEYS;
- if (testVectorHmacAndAesSameKeys == null) {
- testVectorHmacAndAesSameKeys = new SecureMessageBuilder()
- .setAssociatedData(TEST_ASSOCIATED_DATA)
- .setDecryptionKeyId(TEST_DKID)
- .setPublicMetadata(TEST_METADATA)
- .setVerificationKeyId(TEST_VKID)
- .buildSignCryptedMessage(
- testKey1,
- SigType.HMAC_SHA256,
- testKey1,
- EncType.AES_256_CBC,
- TEST_MESSAGE).toByteArray();
- }
- printInitializerFor(indent, "TEST_VECTOR_HMAC_AND_AES_SAME_KEYS", testVectorHmacAndAesSameKeys);
-
- byte[] testVectorHmacAndAesDifferentKeys = TEST_VECTOR_HMAC_AND_AES_DIFFERENT_KEYS;
- if (testVectorHmacAndAesDifferentKeys == null) {
- testVectorHmacAndAesDifferentKeys = new SecureMessageBuilder()
- .setAssociatedData(TEST_ASSOCIATED_DATA)
- .setDecryptionKeyId(TEST_DKID)
- .setPublicMetadata(TEST_METADATA)
- .setVerificationKeyId(TEST_VKID)
- .buildSignCryptedMessage(
- testKey1,
- SigType.HMAC_SHA256,
- testKey2,
- EncType.AES_256_CBC,
- TEST_MESSAGE).toByteArray();
- }
- printInitializerFor(
- indent, "TEST_VECTOR_HMAC_AND_AES_DIFFERENT_KEYS", testVectorHmacAndAesDifferentKeys);
-
- System.out.printf(
- " } catch (Exception e) {\n throw new RuntimeException(e);\n }\n }\n");
- }
-
- private SecretKey makeAesKey() throws NoSuchAlgorithmException {
- KeyGenerator aesKeygen = KeyGenerator.getInstance("AES");
- aesKeygen.init(256);
- return aesKeygen.generateKey();
- }
-
- private void printInitializerFor(String indent, String name, byte[] value) {
- System.out.printf("%s%s = new byte[] %s;\n",
- indent,
- name,
- byteArrayToJavaCode(indent, value));
- }
-
- private static String byteArrayToJavaCode(String lineIndent, byte[] array) {
- String newline = "\n" + lineIndent + " ";
- String unwrappedArray = Arrays.toString(array).replace("[", "").replace("]", "");
- int wrapAfter = 16;
- int count = wrapAfter;
- StringBuilder result = new StringBuilder("{");
- for (String entry : unwrappedArray.split(" ")) {
- if (++count > wrapAfter) {
- result.append(newline);
- count = 0;
- } else {
- result.append(" ");
- }
- result.append(entry);
- }
- result.append(" }");
- return result.toString();
- }
-
- private static byte[] encodePublicKey(PublicKey pk) {
- return PublicKeyProtoUtil.encodePublicKey(pk).toByteArray();
- }
-
- private static PublicKey parsePublicKey(byte[] encodedPk)
- throws InvalidKeySpecException, InvalidProtocolBufferException {
- GenericPublicKey gpk = GenericPublicKey.parseFrom(encodedPk);
- if (PublicKeyProtoUtil.isLegacyCryptoRequired()
- && gpk.getType() == SecureMessageProto.PublicKeyType.EC_P256) {
- return null;
- }
- return PublicKeyProtoUtil.parsePublicKey(gpk);
- }
-
- private static byte[] encodeEcPrivateKey(PrivateKey sk) {
- return sk.getEncoded();
- }
-
- private static PrivateKey parseEcPrivateKey(byte[] sk) throws InvalidKeySpecException {
- if (PublicKeyProtoUtil.isLegacyCryptoRequired()) {
- return null;
- }
- return EC_KEY_FACTORY.generatePrivate(new PKCS8EncodedKeySpec(sk));
- }
-
- // The following block of code was automatically generated by cut and pasting the output of the
- // generateTestVectorsPseudoTest, which should reliably emit this same test vectors. Please
- // DO NOT DELETE any of these existing test vectors unless you _really_ know what you are doing.
- //
- // --- AUTO GENERATED CODE BEGINS HERE ---
- static {
- try {
- TEST_EC_PUBLIC_KEY = parsePublicKey(new byte[] {
- 8, 1, 18, 70, 10, 33, 0, -109, 9, 5, 8, -89, -3, -68, -86, -19, 17,
- -126, -11, -95, 35, 101, 102, -57, -84, -118, 73, 83, 66, -62, -49, -91, 71, -19,
- 52, 123, 113, 119, 45, 18, 33, 0, -65, -19, 83, -66, -12, 62, 102, -67, 116,
- 64, 42, 55, -84, -101, 90, -106, 113, -89, -30, 57, -112, 96, -99, -126, 14, 83,
- 41, 95, -24, -114, 23, -5 });
- TEST_EC_PRIVATE_KEY = parseEcPrivateKey(new byte[] {
- 48, 65, 2, 1, 0, 48, 19, 6, 7, 42, -122, 72, -50, 61, 2, 1, 6,
- 8, 42, -122, 72, -50, 61, 3, 1, 7, 4, 39, 48, 37, 2, 1, 1, 4,
- 32, 26, -82, -61, -86, -59, -8, 2, -62, -17, -20, 122, 3, 85, -102, -76, 81,
- 51, 39, -9, 12, 99, -117, 127, 19, 121, 109, -31, -49, 110, 121, 76, -107 });
- TEST_KEY1 = new SecretKeySpec(new byte[] {
- -89, 105, 62, -41, -75, 78, 70, 110, -62, -58, -80, -81, -99, -62, 39, 38, 37,
- -7, -112, -83, 81, 23, 125, -72, -100, 103, -34, -23, -68, 21, -46, -104 }, "AES");
- TEST_KEY2 = new SecretKeySpec(new byte[] {
- -6, 48, 107, 61, -99, -89, 111, 33, 70, 54, -13, 111, 81, -120, 50, 89, -119,
- -113, -114, 63, 12, -68, 40, 42, -77, -58, -49, 18, 69, 91, -20, -65 }, "AES");
- TEST_VECTOR_ECDSA_ONLY = new byte[] {
- 10, 56, 10, 32, 8, 2, 16, 1, 26, 3, 0, 0, 1, 50, 19, 10, 11,
- 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28,
- 56, 5, 18, 20, 0, 99, 1, 98, 2, 97, 3, 96, 4, 95, 5, 94, 6,
- 93, 7, 92, 8, 91, 9, 90, 18, 72, 48, 70, 2, 33, 0, -79, 59, 50,
- 21, 54, 61, -92, 77, -34, -77, -45, -105, 107, -28, -19, 91, -78, 120, 68, 33,
- 11, -76, -1, 50, 64, -127, -78, 6, 108, 115, -13, 126, 2, 33, 0, -72, -44,
- 52, 93, 105, 109, -127, -111, 11, 33, -111, 97, -114, 9, 117, -68, -45, 64, 63,
- 43, 60, -44, -89, -107, -59, -45, 56, 100, -66, -40, 46, -60 };
- TEST_VECTOR_ECDSA_AND_AES = new byte[] {
- 10, 107, 10, 55, 8, 2, 16, 2, 26, 3, 0, 0, 1, 34, 3, -1, -1,
- 0, 42, 16, -86, 16, 55, -8, -85, -47, -77, -36, -127, 44, -10, -44, -63, 115,
- -111, 26, 50, 19, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
- 23, 24, 25, 26, 27, 28, 56, 5, 18, 48, -110, 23, -67, 122, -118, 96, -4,
- 32, -113, -104, -107, -16, 76, 37, -61, -67, -63, 90, 38, 96, -47, -105, 56, -34,
- 50, -30, 82, 25, 100, 36, 69, 50, 68, 60, 38, 96, -108, -49, -73, -10, -62,
- -76, -45, -105, -86, 93, 28, 34, 18, 70, 48, 68, 2, 33, 0, -87, -103, 11,
- -70, 34, 33, -41, 90, -83, -74, 19, -13, 127, -43, -116, -32, 88, -13, 125, -122,
- 56, -21, 79, 47, 101, 89, -80, -43, 102, 92, 4, -15, 2, 31, 109, -69, 35,
- 21, 44, -27, -77, 32, 17, -90, -68, 113, 55, -24, -122, 40, 81, 51, 0, -84,
- -29, -12, -26, 73, 105, -32, 116, -28, 84, -116, -117 };
- TEST_VECTOR_HMAC_AND_AES_SAME_KEYS = new byte[] {
- 10, 91, 10, 55, 8, 1, 16, 2, 26, 3, 0, 0, 1, 34, 3, -1, -1,
- 0, 42, 16, -110, 48, 67, 67, -31, 24, -42, 13, -44, -109, 6, 113, 34, -70,
- 121, 6, 50, 19, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
- 23, 24, 25, 26, 27, 28, 56, 5, 18, 32, -44, -102, -16, 123, 113, -75, 88,
- -33, 118, 25, 60, -65, 109, 26, -70, -123, 58, -114, 126, 8, 106, -28, 65, -38,
- -4, 68, -78, -91, 49, -13, 22, -122, 18, 32, 20, -120, -113, -76, 85, -35, -53,
- 37, -18, 66, -38, 32, 10, 30, 89, 112, -39, -27, 24, 93, -36, -100, -127, -79,
- 94, -7, -19, -41, -47, -29, 1, 12 };
- TEST_VECTOR_HMAC_AND_AES_DIFFERENT_KEYS = new byte[] {
- 10, 107, 10, 55, 8, 1, 16, 2, 26, 3, 0, 0, 1, 34, 3, -1, -1,
- 0, 42, 16, -96, -7, 39, 79, -37, 40, 1, -30, 97, 0, 123, -7, -124, -75,
- -127, -18, 50, 19, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
- 23, 24, 25, 26, 27, 28, 56, 5, 18, 48, 90, 40, -48, -113, 84, -32, 47,
- 98, 54, -128, 127, 115, 32, 87, -86, 4, -26, 99, 9, -88, 13, 77, 127, 114,
- -48, -117, -94, 96, -86, -105, -123, 11, 116, -69, -83, -110, 3, -10, 0, -34, 72,
- 10, -58, 3, -119, -94, 23, -114, 18, 32, -25, -126, 95, 125, -110, -62, -36, -78,
- 97, 72, -54, -114, 97, -68, -46, 107, 53, 55, -57, 88, 127, -20, -23, 80, -9,
- -91, 115, 42, 24, 49, -76, -111 };
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-}
diff --git a/src/main/javatest/com/google/security/cryptauth/lib/securemessage/SecureMessageTest.java b/src/main/javatest/com/google/security/cryptauth/lib/securemessage/SecureMessageTest.java
deleted file mode 100644
index 40e5091..0000000
--- a/src/main/javatest/com/google/security/cryptauth/lib/securemessage/SecureMessageTest.java
+++ /dev/null
@@ -1,766 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.security.cryptauth.lib.securemessage;
-
-import com.google.protobuf.ByteString;
-import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.protobuf.UninitializedMessageException;
-import com.google.security.cryptauth.lib.securemessage.CryptoOps.EncType;
-import com.google.security.cryptauth.lib.securemessage.CryptoOps.SigType;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.EcP256PublicKey;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.GenericPublicKey;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.Header;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.HeaderAndBody;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.SecureMessage;
-import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.SimpleRsaPublicKey;
-import java.security.InvalidKeyException;
-import java.security.Key;
-import java.security.KeyPair;
-import java.security.NoSuchAlgorithmException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.SecureRandom;
-import java.security.SignatureException;
-import java.security.spec.InvalidKeySpecException;
-import java.util.Arrays;
-import java.util.List;
-import javax.crypto.KeyGenerator;
-import javax.crypto.SecretKey;
-import junit.framework.TestCase;
-
-/**
- * Tests for the SecureMessageBuilder and SecureMessageParser classes.
- */
-public class SecureMessageTest extends TestCase {
- // Not to be used when generating cross-platform test vectors (due to default charset encoding)
- public static final byte[] TEST_MESSAGE =
- "Testing 1 2 3... Testing 1 2 3... Testing 1 2 3...".getBytes();
-
- private static final byte[] TEST_KEY_ID =
- { 0, 1, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };
- // Not to be used when generating cross-platform test vectors (due to default charset encoding)
- private static final byte[] TEST_METADATA = "Some protocol metadata string goes here".getBytes();
- private static final byte[] TEST_ASSOCIATED_DATA = {
- 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0, 9, 0, 10, 0, 11 };
- private static final byte[] ZERO_BYTE = { 0 };
- private static final byte[] EMPTY_BYTES = { };
-
- private static final List<byte[]> MESSAGE_VALUES = Arrays.asList(
- EMPTY_BYTES,
- TEST_MESSAGE
- );
-
- private static final List<byte[]> KEY_ID_VALUES = Arrays.asList(
- null,
- EMPTY_BYTES,
- TEST_KEY_ID);
-
- private static final List<byte[]> METADATA_VALUES = Arrays.asList(
- null,
- EMPTY_BYTES,
- TEST_METADATA
- );
-
- private static final List<byte[]> ASSOCIATED_DATA_VALUES = Arrays.asList(
- null,
- ZERO_BYTE,
- TEST_ASSOCIATED_DATA);
-
- private byte[] message;
- private byte[] metadata;
- private byte[] verificationKeyId;
- private byte[] decryptionKeyId;
- private byte[] associatedData;
- private PublicKey ecPublicKey;
- private PrivateKey ecPrivateKey;
- private PublicKey rsaPublicKey;
- private PrivateKey rsaPrivateKey;
- private SecretKey aesEncryptionKey;
- private SecretKey hmacKey;
- private SecureMessageBuilder secureMessageBuilder = new SecureMessageBuilder();
- private SecureRandom rng = new SecureRandom();
-
- @Override
- public void setUp() {
- message = TEST_MESSAGE;
- metadata = null;
- verificationKeyId = null;
- decryptionKeyId = null;
- associatedData = null;
- if (!PublicKeyProtoUtil.isLegacyCryptoRequired()) {
- KeyPair ecKeyPair = PublicKeyProtoUtil.generateEcP256KeyPair();
- ecPublicKey = ecKeyPair.getPublic();
- ecPrivateKey = ecKeyPair.getPrivate();
- }
- KeyPair rsaKeyPair = PublicKeyProtoUtil.generateRSA2048KeyPair();
- rsaPublicKey = rsaKeyPair.getPublic();
- rsaPrivateKey = rsaKeyPair.getPrivate();
- try {
- aesEncryptionKey = makeAesKey();
- hmacKey = makeAesKey();
- } catch (NoSuchAlgorithmException e) {
- e.printStackTrace();
- fail();
- }
- secureMessageBuilder.reset();
- }
-
- private SecureMessage sign(SigType sigType) throws NoSuchAlgorithmException, InvalidKeyException {
- return getPreconfiguredBuilder().buildSignedCleartextMessage(
- getSigningKeyFor(sigType), sigType, message);
- }
-
- private SecureMessage signCrypt(SigType sigType, EncType encType)
- throws NoSuchAlgorithmException, InvalidKeyException {
- return getPreconfiguredBuilder().buildSignCryptedMessage(
- getSigningKeyFor(sigType), sigType, aesEncryptionKey, encType, message);
- }
-
- private void verify(SecureMessage signed, SigType sigType)
- throws NoSuchAlgorithmException, InvalidKeyException, SignatureException,
- InvalidProtocolBufferException {
- HeaderAndBody headerAndBody = SecureMessageParser.parseSignedCleartextMessage(
- signed,
- getVerificationKeyFor(sigType),
- sigType,
- associatedData);
- consistencyCheck(signed, headerAndBody, sigType, EncType.NONE);
- }
-
- private void verifyDecrypt(SecureMessage encryptedAndSigned, SigType sigType, EncType encType)
- throws InvalidProtocolBufferException, InvalidKeyException, NoSuchAlgorithmException,
- SignatureException {
- HeaderAndBody headerAndBody = SecureMessageParser.parseSignCryptedMessage(
- encryptedAndSigned,
- getVerificationKeyFor(sigType),
- sigType,
- aesEncryptionKey,
- encType,
- associatedData);
- consistencyCheck(encryptedAndSigned, headerAndBody, sigType, encType);
- }
-
- // A collection of different kinds of "alterations" that can be made to SecureMessage protos.
- enum Alteration {
- DECRYPTION_KEY_ID,
- ENCTYPE,
- HEADER_AND_BODY_PROTO,
- MESSAGE,
- METADATA,
- RESIGNCRYPTION_ATTACK,
- SIGTYPE,
- VERIFICATION_KEY_ID,
- ASSOCIATED_DATA_LENGTH,
- }
-
- private void doSignAndVerify(SigType sigType) throws Exception {
- System.out.println("BEGIN_TEST -- Testing SigType: " + sigType + " with:");
- System.out.println("VerificationKeyId: " + Arrays.toString(verificationKeyId));
- System.out.println("Metadata: " + Arrays.toString(metadata));
- System.out.println("AssociatedData: " + Arrays.toString(associatedData));
- System.out.println("Message: " + Arrays.toString(message));
- // Positive test cases
- SecureMessage signed = sign(sigType);
- verify(signed, sigType);
-
- // Negative test cases
- for (Alteration altType : getAlterationsToTest()) {
- System.out.println("Testing alteration: " + altType.toString());
- SecureMessage modified = modifyMessage(signed, altType);
- try {
- verify(modified, sigType);
- fail(altType.toString());
- } catch (SignatureException e) {
- // We expect this
- }
- }
-
- // Try verifying with the wrong associated data
- if ((associatedData == null) || (associatedData.length == 0)) {
- associatedData = ZERO_BYTE;
- } else {
- associatedData = null;
- }
- try {
- verify(signed, sigType);
- fail("Expected verification to fail due to incorrect associatedData");
- } catch (SignatureException e) {
- // We expect this
- }
-
- System.out.println("PASS_TEST -- Testing SigType: " + sigType);
- }
-
- private List<Alteration> getAlterationsToTest() {
- if (isRunningInAndroid()) {
- // Android is very slow. Only try one alteration attack, intead of all of them.
- int randomAlteration = Math.abs(rng.nextInt()) % Alteration.values().length;
- return Arrays.asList(Alteration.values()[randomAlteration]);
- } else {
- // Just try all of them
- return Arrays.asList(Alteration.values());
- }
- }
-
- private void doSignCryptAndVerifyDecrypt(SigType sigType) throws Exception {
- // For now, EncType is always AES_256_CBC
- EncType encType = EncType.AES_256_CBC;
- System.out.println("BEGIN_TEST -- Testing SigType: " + sigType
- + " EncType: " + encType + " with:");
- System.out.println("DecryptionKeyId: " + Arrays.toString(decryptionKeyId));
- System.out.println("VerificationKeyId: " + Arrays.toString(verificationKeyId));
- System.out.println("Metadata: " + Arrays.toString(metadata));
- System.out.println("AssociatedData: " + Arrays.toString(associatedData));
- System.out.println("Message: " + Arrays.toString(message));
- SecureMessage encryptedAndSigned = null;
- encryptedAndSigned = signCrypt(sigType, encType);
- verifyDecrypt(encryptedAndSigned, sigType, encType);
-
- // Negative test cases
- for (Alteration altType : getAlterationsToTest()) {
- if (skipAlterationTestFor(altType, sigType)) {
- System.out.println("Skipping alteration test: " + altType.toString());
- continue;
- }
-
- System.out.println("Testing alteration: " + altType.toString());
- SecureMessage modified = modifyMessage(encryptedAndSigned, altType);
- try {
- verifyDecrypt(modified, sigType, encType);
- fail();
- } catch (SignatureException e) {
- // We expect this
- }
- }
- System.out.println("PASS_TEST -- Testing SigType: " + sigType + " EncType: " + encType);
- }
-
- private boolean skipAlterationTestFor(Alteration altType, SigType sigType) {
- // The RESIGNCRYPTION_ATTACK may be allowed to succeed iff the same symmetric key
- // is being reused for both signature and encryption.
- return (altType == Alteration.RESIGNCRYPTION_ATTACK)
- // Intentionally testing equality of object address here
- && (getVerificationKeyFor(sigType) == aesEncryptionKey);
- }
-
- private SecureMessage modifyMessage(SecureMessage original, Alteration altType) throws Exception {
- ByteString bogus = ByteString.copyFromUtf8("BOGUS");
- HeaderAndBody origHAB = HeaderAndBody.parseFrom(original.getHeaderAndBody());
- HeaderAndBody.Builder newHAB = HeaderAndBody.newBuilder(origHAB);
- Header.Builder newHeader = Header.newBuilder(origHAB.getHeader());
- Header origHeader = origHAB.getHeader();
- SecureMessage.Builder result = SecureMessage.newBuilder(original);
- switch (altType) {
- case DECRYPTION_KEY_ID:
- if (origHeader.hasDecryptionKeyId()) {
- newHeader.clearDecryptionKeyId();
- } else {
- newHeader.setDecryptionKeyId(ByteString.copyFrom(TEST_KEY_ID));
- }
- break;
- case ENCTYPE:
- if (origHeader.getEncryptionScheme() == SecureMessageProto.EncScheme.NONE) {
- newHeader.setEncryptionScheme(SecureMessageProto.EncScheme.AES_256_CBC);
- } else {
- newHeader.setEncryptionScheme(SecureMessageProto.EncScheme.NONE);
- }
- break;
- case HEADER_AND_BODY_PROTO:
- // Substitute a junk byte string instead of the HeeaderAndBody proto message
- return result.setHeaderAndBody(bogus).build();
- case MESSAGE:
- byte[] origBody = origHAB.getBody().toByteArray();
- if (origBody.length > 0) {
- // Lop off trailing byte of the body
- byte[] truncatedBody = CryptoOps.subarray(origBody, 0, origBody.length - 1);
- newHAB.setBody(ByteString.copyFrom(truncatedBody));
- } else {
- newHAB.setBody(bogus);
- }
- break;
- case METADATA:
- if (origHeader.hasPublicMetadata()) {
- newHeader.clearPublicMetadata();
- } else {
- newHeader.setPublicMetadata(bogus);
- }
- break;
- case RESIGNCRYPTION_ATTACK:
- // Simulate stripping a signature, and re-signing a message to see if it will be decrypted.
- newHeader
- .setVerificationKeyId(bogus)
- // In case original was cleartext
- .setEncryptionScheme(SecureMessageProto.EncScheme.AES_256_CBC);
-
- // Now that we've mildly changed the header, compute a new signature for it.
- newHAB.setHeader(newHeader.build());
- byte[] headerAndBodyBytes = newHAB.build().toByteArray();
- result.setHeaderAndBody(ByteString.copyFrom(headerAndBodyBytes));
- SigType sigType = SigType.valueOf(origHeader.getSignatureScheme());
- // Note that in all cases where this attack applies, the associatedData is not normally
- // used directly inside the signature (but rather inside the inner ciphertext).
- result.setSignature(ByteString.copyFrom(CryptoOps.sign(
- sigType, getSigningKeyFor(sigType), rng, headerAndBodyBytes)));
- return result.build();
- case SIGTYPE:
- if (origHeader.getSignatureScheme() == SecureMessageProto.SigScheme.ECDSA_P256_SHA256) {
- newHeader
- .setSignatureScheme(SecureMessageProto.SigScheme.HMAC_SHA256);
- } else {
- newHeader
- .setSignatureScheme(SecureMessageProto.SigScheme.ECDSA_P256_SHA256);
- }
- break;
- case VERIFICATION_KEY_ID:
- if (origHeader.hasVerificationKeyId()) {
- newHeader.clearVerificationKeyId();
- } else {
- newHeader.setVerificationKeyId(
- ByteString.copyFrom(TEST_KEY_ID));
- }
- break;
- case ASSOCIATED_DATA_LENGTH:
- int adLength = origHeader.getAssociatedDataLength();
- switch (adLength) {
- case 0:
- newHeader.setAssociatedDataLength(1);
- break;
- case 1:
- newHeader.setAssociatedDataLength(0);
- break;
- default:
- newHeader.setAssociatedDataLength(adLength - 1);
- }
- break;
- default:
- fail("Forgot to implement an alteration attack: " + altType);
- break;
- }
- // Set the header.
- newHAB.setHeader(newHeader.build());
-
- return result.setHeaderAndBody(ByteString.copyFrom(newHAB.build().toByteArray()))
- .build();
- }
-
- public void testEcDsaSignedOnly() throws Exception {
- doTestSignedOnly(SigType.ECDSA_P256_SHA256);
- }
-
- public void testRsaSignedOnly() throws Exception {
- doTestSignedOnly(SigType.RSA2048_SHA256);
- }
-
- public void testHmacSignedOnly() throws Exception {
- doTestSignedOnly(SigType.HMAC_SHA256);
- }
-
- private void doTestSignedOnly(SigType sigType) throws Exception {
- if (isUnsupported(sigType)) {
- return;
- }
-
- // decryptionKeyId must be left null for signature-only operation
- for (byte[] vkId : KEY_ID_VALUES) {
- verificationKeyId = vkId;
- for (byte[] md : METADATA_VALUES) {
- metadata = md;
- for (byte[] ad : ASSOCIATED_DATA_VALUES) {
- associatedData = ad;
- for (byte[] msg : MESSAGE_VALUES) {
- message = msg;
- doSignAndVerify(sigType);
- }
- }
- }
- }
-
- // Test that use of a DecryptionKeyId is not allowed for signature-only
- try {
- decryptionKeyId = TEST_KEY_ID; // Should trigger a failure
- doSignAndVerify(sigType);
- fail();
- } catch (IllegalStateException expected) {
- }
-}
-
- public void testEncryptedAndMACed() throws Exception {
- for (byte[] dkId : KEY_ID_VALUES) {
- decryptionKeyId = dkId;
- for (byte[] vkId : KEY_ID_VALUES) {
- verificationKeyId = vkId;
- for (byte[] md : METADATA_VALUES) {
- metadata = md;
- for (byte[] ad : ASSOCIATED_DATA_VALUES) {
- associatedData = ad;
- for (byte[] msg : MESSAGE_VALUES) {
- message = msg;
- doSignCryptAndVerifyDecrypt(SigType.HMAC_SHA256);
- }
- }
- }
- }
- }
- }
-
- public void testEncryptedAndMACedWithSameKey() throws Exception {
- hmacKey = aesEncryptionKey; // Re-use the same key for both
- testEncryptedAndMACed();
- }
-
- public void testEncryptedAndEcdsaSigned() throws Exception {
- doTestEncryptedAndSigned(SigType.ECDSA_P256_SHA256);
- }
-
- public void testEncryptedAndRsaSigned() throws Exception {
- doTestEncryptedAndSigned(SigType.RSA2048_SHA256);
- }
-
- public void doTestEncryptedAndSigned(SigType sigType) throws Exception {
- if (isUnsupported(sigType)) {
- return; // EC operations aren't supported on older Android releases
- }
-
- for (byte[] dkId : KEY_ID_VALUES) {
- decryptionKeyId = dkId;
- for (byte[] vkId : KEY_ID_VALUES) {
- verificationKeyId = vkId;
- if ((verificationKeyId == null) && sigType.isPublicKeyScheme()) {
- continue; // Null verificationKeyId is not allowed with public key signcryption
- }
- for (byte[] md : METADATA_VALUES) {
- metadata = md;
- for (byte[] ad : ASSOCIATED_DATA_VALUES) {
- associatedData = ad;
- for (byte[] msg : MESSAGE_VALUES) {
- message = msg;
- doSignCryptAndVerifyDecrypt(sigType);
- }
- }
- }
- }
- }
-
- // Verify that a missing verificationKeyId is not allowed here
- try {
- verificationKeyId = null; // Should trigger a failure
- signCrypt(sigType, EncType.AES_256_CBC);
- fail();
- } catch (IllegalStateException expected) {
- }
- }
-
- public void testSignCryptionRequiresEncryption() throws Exception {
- try {
- signCrypt(SigType.RSA2048_SHA256, EncType.NONE);
- } catch (IllegalArgumentException expected) {
- }
- }
-
- public void testAssociatedData() throws Exception {
- // How much extra room might the encoding of AssociatedDataLength take up?
- int maxAssociatedDataOverheadBytes = 4;
- // How many bytes might normally vary in the encoding length for SecureMessages generated with
- // fresh randomness but identical contents (e.g., due to MSBs being 0)
- int maxJitter = 2;
- verificationKeyId = TEST_KEY_ID; // So that public key signcryption will work
- message = TEST_MESSAGE;
-
- for (SigType sigType : SigType.values()) {
- if (isUnsupported(sigType)) {
- continue;
- }
- associatedData = null;
- SecureMessage signed = sign(sigType);
- int signedLength = signed.toByteArray().length;
- associatedData = EMPTY_BYTES;
- // Check that EMPTY_BYTES is equivalent to null associated data under verification
- verify(signed, sigType);
- // We already tested that incorrect associated data fails elsewhere in negative test cases
- associatedData = TEST_ASSOCIATED_DATA;
- SecureMessage signedWithAssociatedData = sign(sigType);
- int signedWithAssociatedDataLength = signedWithAssociatedData.toByteArray().length;
- String logInfo = "Testing associated data overhead for signature using: " + sigType
- + " signedLength=" + signedLength
- + " signedWithAssociatedDataLength=" + signedWithAssociatedDataLength;
- System.out.println(logInfo);
- assertTrue(logInfo,
- signedWithAssociatedData.toByteArray().length
- <= signed.toByteArray().length + maxAssociatedDataOverheadBytes + maxJitter);
- }
-
- for (SigType sigType : SigType.values()) {
- if (isUnsupported(sigType)) {
- continue;
- }
- associatedData = null;
- SecureMessage signCrypted = signCrypt(sigType, EncType.AES_256_CBC);
- int signCryptedLength = signCrypted.toByteArray().length;
- // Check that EMPTY_BYTES is equivalent to null associated data under verification
- associatedData = EMPTY_BYTES;
- verifyDecrypt(signCrypted, sigType, EncType.AES_256_CBC);
- // We already tested that incorrect associated data fails elsewhere in negative test cases
- associatedData = TEST_ASSOCIATED_DATA;
- SecureMessage signCryptedWithAssociatedData = signCrypt(sigType, EncType.AES_256_CBC);
- int signCryptedWithAssociatedDataLength = signCryptedWithAssociatedData.toByteArray().length;
- String logInfo = "Testing associated data overhead for signcryption using: " + sigType
- + " signCryptedLength=" + signCryptedLength
- + " signCryptedWithAssociatedDataLength=" + signCryptedWithAssociatedDataLength;
- System.out.println(logInfo);
- assertTrue(logInfo,
- signCryptedWithAssociatedData.toByteArray().length
- <= signCrypted.toByteArray().length + maxAssociatedDataOverheadBytes + maxJitter);
- }
- }
-
- public void testEncryptedAndEcdsaSignedUsingPublicKeyProto() throws Exception {
- if (isUnsupported(SigType.ECDSA_P256_SHA256)) {
- return;
- }
-
- // Safest usage of SignCryption is to set the VerificationKeyId to an actual representation of
- // the verification key.
- verificationKeyId = PublicKeyProtoUtil.encodeEcPublicKey(ecPublicKey).toByteArray();
- SecureMessage encryptedAndSigned = signCrypt(SigType.ECDSA_P256_SHA256, EncType.AES_256_CBC);
-
- // Simulate extracting the verification key ID from the SecureMessage (non-standard usage)
- ecPublicKey =
- PublicKeyProtoUtil.parseEcPublicKey(
- EcP256PublicKey.parseFrom(
- SecureMessageParser.getUnverifiedHeader(encryptedAndSigned)
- .getVerificationKeyId()));
-
- // Note that this verification uses the encoded/decoded ecPublicKey value
- verifyDecrypt(encryptedAndSigned, SigType.ECDSA_P256_SHA256, EncType.AES_256_CBC);
- }
-
- public void testEncryptedAndRsaSignedUsingPublicKeyProto() throws Exception {
- // Safest usage of SignCryption is to set the VerificationKeyId to an actual representation of
- // the verification key.
- verificationKeyId = PublicKeyProtoUtil.encodeRsa2048PublicKey(rsaPublicKey).toByteArray();
- SecureMessage encryptedAndSigned = signCrypt(SigType.RSA2048_SHA256, EncType.AES_256_CBC);
-
- // Simulate extracting the verification key ID from the SecureMessage (non-standard usage)
- rsaPublicKey =
- PublicKeyProtoUtil.parseRsa2048PublicKey(
- SimpleRsaPublicKey.parseFrom(
- SecureMessageParser.getUnverifiedHeader(encryptedAndSigned)
- .getVerificationKeyId()));
-
- // Note that this verification uses the encoded/decoded SimpleRsaPublicKey value
- verifyDecrypt(encryptedAndSigned, SigType.RSA2048_SHA256, EncType.AES_256_CBC);
- }
-
- // TODO(shabsi): The test was only corrupting header but wasn't setting the body. With protolite,
- // not setting a required field causes problems. Modify the SecureMessageParser test and
- // enable/remove this test.
- /*
- public void testCorruptUnverifiedHeader() throws Exception {
- // Create a sample message
- SecureMessage original = signCrypt(SigType.HMAC_SHA256, EncType.AES_256_CBC);
- HeaderAndBody originalHAB = HeaderAndBody.parseFrom(original.getHeaderAndBody().toByteArray());
- for (CorruptHeaderType corruptionType : CorruptHeaderType.values()) {
- // Mess with the HeaderAndBody field
- HeaderAndBody.Builder corruptHAB = HeaderAndBody.newBuilder(originalHAB);
- try {
- corruptHeaderWith(corruptionType, corruptHAB);
- // Construct the corrupted message using the modified HeaderAndBody
- SecureMessage.Builder corrupt = SecureMessage.newBuilder(original);
- corrupt.setHeaderAndBody(ByteString.copyFrom(corruptHAB.build().toByteArray())).build();
- SecureMessageParser.getUnverifiedHeader(corrupt.build());
- fail("Corrupt header type " + corruptionType + " parsed without error");
- } catch (InvalidProtocolBufferException expected) {
- }
- }
- }
- */
-
- public void testParseEmptyMessage() throws Exception {
- byte[] bogusData = new byte[0];
-
- try {
- SecureMessageParser.parseSignedCleartextMessage(
- SecureMessage.parseFrom(bogusData),
- aesEncryptionKey,
- SigType.HMAC_SHA256);
- fail("Empty message verified without error");
- } catch (SignatureException | UninitializedMessageException
- | InvalidProtocolBufferException expected) {
- }
- }
-
- public void testParseKeyInvalidInputs() throws Exception {
- GenericPublicKey[] badKeys = new GenericPublicKey[] {
- GenericPublicKey.newBuilder().setType(SecureMessageProto.PublicKeyType.EC_P256).build(),
- GenericPublicKey.newBuilder().setType(SecureMessageProto.PublicKeyType.RSA2048).build(),
- GenericPublicKey.newBuilder().setType(SecureMessageProto.PublicKeyType.DH2048_MODP).build(),
- };
- for (int i = 0; i < badKeys.length; i++) {
- GenericPublicKey key = badKeys[i];
- try {
- PublicKeyProtoUtil.parsePublicKey(key);
- fail(String.format("%sth key was parsed without exceptions", i));
- } catch (InvalidKeySpecException expected) {
- }
- }
- }
-
- enum CorruptHeaderType {
- EMPTY,
- // TODO(shabsi): Remove these test cases and modify code in SecureMessageParser appropriately.
- // UNSET,
- // JUNK,
- }
-
- private void corruptHeaderWith(CorruptHeaderType corruptionType,
- HeaderAndBody.Builder protoToModify) {
- switch (corruptionType) {
- case EMPTY:
- protoToModify.setHeader(Header.getDefaultInstance());
- break;
- /*
- case JUNK:
- Header.Builder junk = Header.newBuilder();
- junk.setDecryptionKeyId(ByteString.copyFromUtf8("fooooo"));
- junk.setIv(ByteString.copyFromUtf8("bar"));
- // Don't set signature scheme.
- junk.setVerificationKeyId(ByteString.copyFromUtf8("bazzzzz"));
- protoToModify.setHeader(junk.build());
- break;
- case UNSET:
- protoToModify.clearHeader();
- break;
- */
- default:
- throw new RuntimeException("Broken test code");
- }
- }
-
- private void consistencyCheck(
- SecureMessage secmsg, HeaderAndBody headerAndBody, SigType sigType, EncType encType)
- throws InvalidProtocolBufferException {
- Header header = SecureMessageParser.getUnverifiedHeader(secmsg);
- checkHeader(header, sigType, encType); // Checks that the "unverified header" looks right
- checkHeaderAndBody(header, headerAndBody); // Matches header vs. the "verified" headerAndBody
- }
-
- private Header checkHeader(Header header, SigType sigType, EncType encType) {
- assertEquals(sigType.getSigScheme(), header.getSignatureScheme());
- assertEquals(encType.getEncScheme(), header.getEncryptionScheme());
- checkKeyIdsAndMetadata(verificationKeyId, decryptionKeyId, metadata, associatedData, header);
- return header;
- }
-
- private void checkHeaderAndBody(Header header, HeaderAndBody headerAndBody) {
- assertTrue(header.equals(headerAndBody.getHeader()));
- assertTrue(Arrays.equals(message, headerAndBody.getBody().toByteArray()));
- }
-
- private void checkKeyIdsAndMetadata(byte[] verificationKeyId, byte[] decryptionKeyId,
- byte[] metadata, byte[] associatedData, Header header) {
- if (verificationKeyId == null) {
- assertFalse(header.hasVerificationKeyId());
- } else {
- assertTrue(Arrays.equals(verificationKeyId, header.getVerificationKeyId().toByteArray()));
- }
- if (decryptionKeyId == null) {
- assertFalse(header.hasDecryptionKeyId());
- } else {
- assertTrue(Arrays.equals(decryptionKeyId, header.getDecryptionKeyId().toByteArray()));
- }
- if (metadata == null) {
- assertFalse(header.hasPublicMetadata());
- } else {
- assertTrue(Arrays.equals(metadata, header.getPublicMetadata().toByteArray()));
- }
- if (associatedData == null) {
- assertFalse(header.hasAssociatedDataLength());
- } else {
- assertEquals(associatedData.length, header.getAssociatedDataLength());
- }
- }
-
- private SecretKey makeAesKey() throws NoSuchAlgorithmException {
- KeyGenerator aesKeygen = KeyGenerator.getInstance("AES");
- aesKeygen.init(256);
- return aesKeygen.generateKey();
- }
-
- private Key getSigningKeyFor(SigType sigType) {
- if (sigType == SigType.ECDSA_P256_SHA256) {
- return ecPrivateKey;
- }
- if (sigType == SigType.RSA2048_SHA256) {
- return rsaPrivateKey;
- }
- if (sigType == SigType.HMAC_SHA256) {
- return hmacKey;
- }
- return null; // This should not happen
- }
-
- private Key getVerificationKeyFor(SigType sigType) {
- try {
- if (sigType == SigType.ECDSA_P256_SHA256) {
- return PublicKeyProtoUtil.parseEcPublicKey(
- PublicKeyProtoUtil.encodeEcPublicKey(ecPublicKey));
- }
- if (sigType == SigType.RSA2048_SHA256) {
- return PublicKeyProtoUtil.parseRsa2048PublicKey(
- PublicKeyProtoUtil.encodeRsa2048PublicKey(rsaPublicKey));
- }
- } catch (InvalidKeySpecException e) {
- throw new AssertionError(e);
- }
-
- assertFalse(sigType.isPublicKeyScheme());
- // For symmetric key schemes
- return getSigningKeyFor(sigType);
- }
-
- private SecureMessageBuilder getPreconfiguredBuilder() {
- // Re-use a single instance of SecureMessageBuilder for efficiency.
- SecureMessageBuilder builder = secureMessageBuilder.reset();
- if (verificationKeyId != null) {
- builder.setVerificationKeyId(verificationKeyId);
- }
- if (decryptionKeyId != null) {
- builder.setDecryptionKeyId(decryptionKeyId);
- }
- if (metadata != null) {
- builder.setPublicMetadata(metadata);
- }
- if (associatedData != null) {
- builder.setAssociatedData(associatedData);
- }
- return builder;
- }
-
- private static boolean isUnsupported(SigType sigType) {
- // EC operations aren't supported on older Android releases
- return PublicKeyProtoUtil.isLegacyCryptoRequired()
- && (sigType == SigType.ECDSA_P256_SHA256);
- }
-
- private static boolean isRunningInAndroid() {
- try {
- ClassLoader.getSystemClassLoader().loadClass("android.os.Build$VERSION");
- return true;
- } catch (ClassNotFoundException e) {
- // Not running on Android
- return false;
- }
- }
-}
diff --git a/src/main/proto/CMakeLists.txt b/src/main/proto/CMakeLists.txt
deleted file mode 100644
index cd94f3f..0000000
--- a/src/main/proto/CMakeLists.txt
+++ /dev/null
@@ -1,32 +0,0 @@
-# Copyright 2020 Google LLC
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# https://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-add_cc_proto_library(
- proto_device_to_device_messages_cc_proto
- PROTOS device_to_device_messages.proto
- DEPS proto_securemessage_cc_proto
- INCS ${CMAKE_CURRENT_BINARY_DIR}/..
-)
-
-add_cc_proto_library(
- proto_securegcm_cc_proto
- PROTOS securegcm.proto
- INCS ${CMAKE_CURRENT_BINARY_DIR}/..
-)
-
-add_cc_proto_library(
- proto_ukey_cc_proto
- PROTOS ukey.proto
- INCS ${CMAKE_CURRENT_BINARY_DIR}/..
-)
diff --git a/src/main/proto/device_to_device_messages.proto b/src/main/proto/device_to_device_messages.proto
deleted file mode 100644
index 5600373..0000000
--- a/src/main/proto/device_to_device_messages.proto
+++ /dev/null
@@ -1,81 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-syntax = "proto2";
-
-package securegcm;
-
-import "securemessage.proto";
-
-option optimize_for = LITE_RUNTIME;
-option java_package = "com.google.security.cryptauth.lib.securegcm";
-option java_outer_classname = "DeviceToDeviceMessagesProto";
-option objc_class_prefix = "SGCM";
-
-// Used by protocols between devices
-message DeviceToDeviceMessage {
- // the payload of the message
- optional bytes message = 1;
-
- // the sequence number of the message - must be increasing.
- optional int32 sequence_number = 2;
-}
-
-// sent as the first message from initiator to responder
-// in an unauthenticated Diffie-Hellman Key Exchange
-message InitiatorHello {
- // The session public key to send to the responder
- optional securemessage.GenericPublicKey public_dh_key = 1;
-
- // The protocol version
- optional int32 protocol_version = 2 [default = 0];
-}
-
-// sent inside the header of the first message from the responder to the
-// initiator in an unauthenticated Diffie-Hellman Key Exchange
-message ResponderHello {
- // The session public key to send to the initiator
- optional securemessage.GenericPublicKey public_dh_key = 1;
-
- // The protocol version
- optional int32 protocol_version = 2 [default = 0];
-}
-
-// Type of curve
-enum Curve { ED_25519 = 1; }
-
-// A convenience proto for encoding curve points in affine representation
-message EcPoint {
- required Curve curve = 1;
-
- // x and y are encoded in big-endian two's complement
- // client MUST verify (x,y) is a valid point on the specified curve
- required bytes x = 2;
- required bytes y = 3;
-}
-
-message SpakeHandshakeMessage {
- // Each flow in the protocol bumps this counter
- optional int32 flow_number = 1;
-
- // Some (but not all) SPAKE flows send a point on an elliptic curve
- optional EcPoint ec_point = 2;
-
- // Some (but not all) SPAKE flows send a hash value
- optional bytes hash_value = 3;
-
- // The last flow of a SPAKE protocol can send an optional payload,
- // since the key exchange is already complete on the sender's side.
- optional bytes payload = 4;
-}
diff --git a/src/main/proto/passwordless_auth_payloads.proto b/src/main/proto/passwordless_auth_payloads.proto
deleted file mode 100644
index 69c2784..0000000
--- a/src/main/proto/passwordless_auth_payloads.proto
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-syntax = "proto2";
-
-package securegcm;
-
-option optimize_for = LITE_RUNTIME;
-option java_package = "com.google.security.cryptauth.lib.securegcm";
-option java_outer_classname = "SecureGcmPasswordlessAuthProto";
-option objc_class_prefix = "SGCM";
-
-message IdentityAssertion {
- // Browser data contains the challenge, origin, etc.
- optional bytes browser_data_hash = 1;
-
- // A counter that we expect to increase.
- optional int64 counter = 2;
-
- // An integer encoding whether the user actively approved this assertion,
- // or whether the phone auto-issued the assertion.
- // Possible values are:
- // 1: User explicitly approved the login.
- // 0: Phone approved login without consulting the user.
- optional int32 user_approval = 3;
-}
diff --git a/src/main/proto/proximity_payloads.proto b/src/main/proto/proximity_payloads.proto
deleted file mode 100644
index d7a4956..0000000
--- a/src/main/proto/proximity_payloads.proto
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-syntax = "proto2";
-
-package securegcm;
-
-import "securegcm.proto";
-
-option optimize_for = LITE_RUNTIME;
-option java_package = "com.google.security.cryptauth.lib.securegcm";
-option java_outer_classname = "SecureGcmProximityAuthProto";
-option objc_class_prefix = "SGCM";
-
-// Message used when one device wants to initiate a Proximity Auth pairing with
-// another device DEPRECATED. DO NOT USE
-message CloudToDeviceProximityAuthPairing {
- // The name or description of the device that wants to pair with another
- // personal device of the user. This is a string that may be shown to the
- // user or may be kept in logs.
- optional string initiating_device_name = 1;
-
- // The original device's Bluetooth address in human readable form
- // (e.g., <code>AA:BB:CC:DD:EE:FF</code>)
- optional string initiating_device_bt_address = 2;
-
- // A symmetric key that was generated by the original device.
- optional bytes ephemeral_symmetric_key = 3;
-
- // Optional additional metadata that the initiating device can choose to send.
- // Used for quick protocol iteration.
- optional bytes additional_metadata = 4;
-}
-
-// Message to push to eligible unlock devices so that they can contact the
-// device to be unlocked. Used by FindEligibleUnlockDevicesRequest, with
-// PayloadType = DEVICE_PROXIMITY_CALLBACK.
-message DeviceProximityCallback {
- // Required. The bluetooth MAC address that should be contacted by the unlock
- // device.
- optional string callback_bluetooth_address = 1;
-
- // Required. The type of the device that triggered this callback to be sent.
- optional DeviceType source_device_type = 2;
-
- // The version of the setup protocol that the source device expects to use.
- optional int32 protocol_version = 3;
-}
diff --git a/src/main/proto/securegcm.proto b/src/main/proto/securegcm.proto
deleted file mode 100644
index 0325f06..0000000
--- a/src/main/proto/securegcm.proto
+++ /dev/null
@@ -1,308 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-syntax = "proto2";
-
-package securegcm;
-
-option optimize_for = LITE_RUNTIME;
-option java_package = "com.google.security.cryptauth.lib.securegcm";
-option java_outer_classname = "SecureGcmProto";
-option objc_class_prefix = "SGCM";
-
-// Message used only during enrollment
-// Field numbers should be kept in sync with DeviceInfo in:
-// java/com/google/security/cryptauth/backend/services/common/common.proto
-message GcmDeviceInfo {
- // This field's name does not match the one in DeviceInfo for legacy reasons.
- // Consider using long_device_id and device_type instead when enrolling
- // non-android devices.
- optional fixed64 android_device_id = 1;
-
- // Used for device_address of DeviceInfo field 2, but for GCM capable devices.
- optional bytes gcm_registration_id = 102;
-
- // Used for device_address of DeviceInfo field 2, but for iOS devices.
- optional bytes apn_registration_id = 202;
-
- // Does the user have notifications enabled for the given device address.
- optional bool notification_enabled = 203 [default = true];
-
- // Used for device_address of DeviceInfo field 2, a Bluetooth Mac address for
- // the device (e.g., to be used with EasyUnlock)
- optional string bluetooth_mac_address = 302;
-
- // SHA-256 hash of the device master key (from the key exchange).
- // Differs from DeviceInfo field 3, which contains the actual master key.
- optional bytes device_master_key_hash = 103;
-
- // A SecureMessage.EcP256PublicKey
- required bytes user_public_key = 4;
-
- // device's model name
- // (e.g., an android.os.Build.MODEL or UIDevice.model)
- optional string device_model = 7;
-
- // device's locale
- optional string locale = 8;
-
- // The handle for user_public_key (and implicitly, a master key)
- optional bytes key_handle = 9;
-
- // The initial counter value for the device, sent by the device
- optional int64 counter = 12 [default = 0];
-
- // The Operating System version on the device
- // (e.g., an android.os.Build.DISPLAY or UIDevice.systemVersion)
- optional string device_os_version = 13;
-
- // The Operating System version number on the device
- // (e.g., an android.os.Build.VERSION.SDK_INT)
- optional int64 device_os_version_code = 14;
-
- // The Operating System release on the device
- // (e.g., an android.os.Build.VERSION.RELEASE)
- optional string device_os_release = 15;
-
- // The Operating System codename on the device
- // (e.g., an android.os.Build.VERSION.CODENAME or UIDevice.systemName)
- optional string device_os_codename = 16;
-
- // The software version running on the device
- // (e.g., Authenticator app version string)
- optional string device_software_version = 17;
-
- // The software version number running on the device
- // (e.g., Authenticator app version code)
- optional int64 device_software_version_code = 18;
-
- // Software package information if applicable
- // (e.g., com.google.android.apps.authenticator2)
- optional string device_software_package = 19;
-
- // Size of the display in thousandths of an inch (e.g., 7000 mils = 7 in)
- optional int32 device_display_diagonal_mils = 22;
-
- // For Authzen capable devices, their Authzen protocol version
- optional int32 device_authzen_version = 24;
-
- // Not all devices have device identifiers that fit in 64 bits.
- optional bytes long_device_id = 29;
-
- // The device manufacturer name
- // (e.g., android.os.Build.MANUFACTURER)
- optional string device_manufacturer = 31;
-
- // Used to indicate which type of device this is.
- optional DeviceType device_type = 32 [default = ANDROID];
-
- // Fields corresponding to screenlock type/features and hardware features
- // should be numbered in the 400 range.
-
- // Is this device using a secure screenlock (e.g., pattern or pin unlock)
- optional bool using_secure_screenlock = 400 [default = false];
-
- // Is auto-unlocking the screenlock (e.g., when at "home") supported?
- optional bool auto_unlock_screenlock_supported = 401 [default = false];
-
- // Is auto-unlocking the screenlock (e.g., when at "home") enabled?
- optional bool auto_unlock_screenlock_enabled = 402 [default = false];
-
- // Does the device have a Bluetooth (classic) radio?
- optional bool bluetooth_radio_supported = 403 [default = false];
-
- // Is the Bluetooth (classic) radio on?
- optional bool bluetooth_radio_enabled = 404 [default = false];
-
- // Does the device hardware support a mobile data connection?
- optional bool mobile_data_supported = 405 [default = false];
-
- // Does the device support tethering?
- optional bool tethering_supported = 406 [default = false];
-
- // Does the device have a BLE radio?
- optional bool ble_radio_supported = 407 [default = false];
-
- // Is the device a "Pixel Experience" Android device?
- optional bool pixel_experience = 408 [default = false];
-
- // Is the device running in the ARC++ container on a chromebook?
- optional bool arc_plus_plus = 409 [default = false];
-
- // Is the value set in |using_secure_screenlock| reliable? On some Android
- // devices, the platform API to get the screenlock state is not trustworthy.
- // See b/32212161.
- optional bool is_screenlock_state_flaky = 410 [default = false];
-
- // A list of multi-device software features supported by the device.
- repeated SoftwareFeature supported_software_features = 411;
-
- // A list of multi-device software features currently enabled (active) on the
- // device.
- repeated SoftwareFeature enabled_software_features = 412;
-
- // The enrollment session id this is sent with
- optional bytes enrollment_session_id = 1000;
-
- // A copy of the user's OAuth token
- optional string oauth_token = 1001;
-}
-
-// This enum is used by iOS devices as values for device_display_diagonal_mils
-// in GcmDeviceInfo. There is no good way to calculate it on those devices.
-enum AppleDeviceDiagonalMils {
- // This is the mils diagonal on an iPhone 5.
- APPLE_PHONE = 4000;
- // This is the mils diagonal on an iPad mini.
- APPLE_PAD = 7900;
-}
-
-// This should be kept in sync with DeviceType in:
-// java/com/google/security/cryptauth/backend/services/common/common_enums.proto
-enum DeviceType {
- UNKNOWN = 0;
- ANDROID = 1;
- CHROME = 2;
- IOS = 3;
- BROWSER = 4;
- OSX = 5;
-}
-
-// MultiDevice features which may be supported and enabled on a device. See
-enum SoftwareFeature {
- UNKNOWN_FEATURE = 0;
- BETTER_TOGETHER_HOST = 1;
- BETTER_TOGETHER_CLIENT = 2;
- EASY_UNLOCK_HOST = 3;
- EASY_UNLOCK_CLIENT = 4;
- MAGIC_TETHER_HOST = 5;
- MAGIC_TETHER_CLIENT = 6;
- SMS_CONNECT_HOST = 7;
- SMS_CONNECT_CLIENT = 8;
-}
-
-// A list of "reasons" that can be provided for calling server-side APIs.
-// This is particularly important for calls that can be triggered by different
-// kinds of events. Please try to keep reasons as generic as possible, so that
-// codes can be re-used by various callers in a sensible fashion.
-enum InvocationReason {
- REASON_UNKNOWN = 0;
- // First run of the software package invoking this call
- REASON_INITIALIZATION = 1;
- // Ordinary periodic actions (e.g. monthly master key rotation)
- REASON_PERIODIC = 2;
- // Slow-cycle periodic action (e.g. yearly keypair rotation???)
- REASON_SLOW_PERIODIC = 3;
- // Fast-cycle periodic action (e.g. daily sync for Smart Lock users)
- REASON_FAST_PERIODIC = 4;
- // Expired state (e.g. expired credentials, or cached entries) was detected
- REASON_EXPIRATION = 5;
- // An unexpected protocol failure occurred (so attempting to repair state)
- REASON_FAILURE_RECOVERY = 6;
- // A new account has been added to the device
- REASON_NEW_ACCOUNT = 7;
- // An existing account on the device has been changed
- REASON_CHANGED_ACCOUNT = 8;
- // The user toggled the state of a feature (e.g. Smart Lock enabled via BT)
- REASON_FEATURE_TOGGLED = 9;
- // A "push" from the server caused this action (e.g. a sync tickle)
- REASON_SERVER_INITIATED = 10;
- // A local address change triggered this (e.g. GCM registration id changed)
- REASON_ADDRESS_CHANGE = 11;
- // A software update has triggered this
- REASON_SOFTWARE_UPDATE = 12;
- // A manual action by the user triggered this (e.g. commands sent via adb)
- REASON_MANUAL = 13;
- // A custom key has been invalidated on the device (e.g. screen lock is
- // disabled).
- REASON_CUSTOM_KEY_INVALIDATION = 14;
- // Periodic action triggered by auth_proximity
- REASON_PROXIMITY_PERIODIC = 15;
-}
-
-enum Type {
- ENROLLMENT = 0;
- TICKLE = 1;
- TX_REQUEST = 2;
- TX_REPLY = 3;
- TX_SYNC_REQUEST = 4;
- TX_SYNC_RESPONSE = 5;
- TX_PING = 6;
- DEVICE_INFO_UPDATE = 7;
- TX_CANCEL_REQUEST = 8;
-
- // DEPRECATED (can be re-used after Aug 2015)
- PROXIMITYAUTH_PAIRING = 10;
-
- // The kind of identity assertion generated by a "GCM V1" device (i.e.,
- // an Android phone that has registered with us a public and a symmetric
- // key)
- GCMV1_IDENTITY_ASSERTION = 11;
-
- // Device-to-device communications are protected by an unauthenticated
- // Diffie-Hellman exchange. The InitiatorHello message is simply the
- // initiator's public DH key, and is not encoded as a SecureMessage, so
- // it doesn't have a tag.
- // The ResponderHello message (which is sent by the responder
- // to the initiator), on the other hand, carries a payload that is protected
- // by the derived shared key. It also contains the responder's
- // public DH key. ResponderHelloAndPayload messages have the
- // DEVICE_TO_DEVICE_RESPONDER_HELLO tag.
- DEVICE_TO_DEVICE_RESPONDER_HELLO_PAYLOAD = 12;
-
- // Device-to-device communications are protected by an unauthenticated
- // Diffie-Hellman exchange. Once the initiator and responder
- // agree on a shared key (through Diffie-Hellman), they will use messages
- // tagged with DEVICE_TO_DEVICE_MESSAGE to exchange data.
- DEVICE_TO_DEVICE_MESSAGE = 13;
-
- // Notification to let a device know it should contact a nearby device.
- DEVICE_PROXIMITY_CALLBACK = 14;
-
- // Device-to-device communications are protected by an unauthenticated
- // Diffie-Hellman exchange. During device-to-device authentication, the first
- // message from initiator (the challenge) is signed and put into the payload
- // of the message sent back to the initiator.
- UNLOCK_KEY_SIGNED_CHALLENGE = 15;
-
- // Specialty (corp only) features
- LOGIN_NOTIFICATION = 101;
-}
-
-message GcmMetadata {
- required Type type = 1;
- optional int32 version = 2 [default = 0];
-}
-
-message Tickle {
- // Time after which this tickle should expire
- optional fixed64 expiry_time = 1;
-}
-
-message LoginNotificationInfo {
- // Time at which the server received the login notification request.
- optional fixed64 creation_time = 2;
-
- // Must correspond to user_id in LoginNotificationRequest, if set.
- optional string email = 3;
-
- // Host where the user's credentials were used to login, if meaningful.
- optional string host = 4;
-
- // Location from where the user's credentials were used, if meaningful.
- optional string source = 5;
-
- // Type of login, e.g. ssh, gnome-screensaver, or web.
- optional string event_type = 6;
-}
diff --git a/src/main/proto/securemessage.proto b/src/main/proto/securemessage.proto
deleted file mode 100644
index 5118d35..0000000
--- a/src/main/proto/securemessage.proto
+++ /dev/null
@@ -1,126 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Proto definitions for SecureMessage format
-
-syntax = "proto2";
-
-package securemessage;
-
-option optimize_for = LITE_RUNTIME;
-option java_package = "com.google.security.cryptauth.lib.securemessage";
-option java_outer_classname = "SecureMessageProto";
-option objc_class_prefix = "SMSG";
-
-message SecureMessage {
- // Must contain a HeaderAndBody message
- required bytes header_and_body = 1;
- // Signature of header_and_body
- required bytes signature = 2;
-}
-
-// Supported "signature" schemes (both symmetric key and public key based)
-enum SigScheme {
- HMAC_SHA256 = 1;
- ECDSA_P256_SHA256 = 2;
- // Not recommended -- use ECDSA_P256_SHA256 instead
- RSA2048_SHA256 = 3;
-}
-
-// Supported encryption schemes
-enum EncScheme {
- // No encryption
- NONE = 1;
- AES_256_CBC = 2;
-}
-
-message Header {
- required SigScheme signature_scheme = 1;
- required EncScheme encryption_scheme = 2;
- // Identifies the verification key
- optional bytes verification_key_id = 3;
- // Identifies the decryption key
- optional bytes decryption_key_id = 4;
- // Encryption may use an IV
- optional bytes iv = 5;
- // Arbitrary per-protocol public data, to be sent with the plain-text header
- optional bytes public_metadata = 6;
- // The length of some associated data this is not sent in this SecureMessage,
- // but which will be bound to the signature.
- optional uint32 associated_data_length = 7 [default = 0];
-}
-
-message HeaderAndBody {
- // Public data about this message (to be bound in the signature)
- required Header header = 1;
- // Payload data
- required bytes body = 2;
-}
-
-// Must be kept wire-format compatible with HeaderAndBody. Provides the
-// SecureMessage code with a consistent wire-format representation that
-// remains stable irrespective of protobuf implementation choices. This
-// low-level representation of a HeaderAndBody should not be used by
-// any code outside of the SecureMessage library implementation/tests.
-message HeaderAndBodyInternal {
- // A raw (wire-format) byte encoding of a Header, suitable for hashing
- required bytes header = 1;
- // Payload data
- required bytes body = 2;
-}
-
-// -------
-// The remainder of the messages defined here are provided only for
-// convenience. They are not needed for SecureMessage proper, but are
-// commonly useful wherever SecureMessage might be applied.
-// -------
-
-// A list of supported public key types
-enum PublicKeyType {
- EC_P256 = 1;
- RSA2048 = 2;
- // 2048-bit MODP group 14, from RFC 3526
- DH2048_MODP = 3;
-}
-
-// A convenience proto for encoding NIST P-256 elliptic curve public keys
-message EcP256PublicKey {
- // x and y are encoded in big-endian two's complement (slightly wasteful)
- // Client MUST verify (x,y) is a valid point on NIST P256
- required bytes x = 1;
- required bytes y = 2;
-}
-
-// A convenience proto for encoding RSA public keys with small exponents
-message SimpleRsaPublicKey {
- // Encoded in big-endian two's complement
- required bytes n = 1;
- optional int32 e = 2 [default = 65537];
-}
-
-// A convenience proto for encoding Diffie-Hellman public keys,
-// for use only when Elliptic Curve based key exchanges are not possible.
-// (Note that the group parameters must be specified separately)
-message DhPublicKey {
- // Big-endian two's complement encoded group element
- required bytes y = 1;
-}
-
-message GenericPublicKey {
- required PublicKeyType type = 1;
- optional EcP256PublicKey ec_p256_public_key = 2;
- optional SimpleRsaPublicKey rsa2048_public_key = 3;
- // Use only as a last resort
- optional DhPublicKey dh2048_public_key = 4;
-}
diff --git a/src/main/proto/ukey.proto b/src/main/proto/ukey.proto
deleted file mode 100644
index 327d8d3..0000000
--- a/src/main/proto/ukey.proto
+++ /dev/null
@@ -1,105 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-syntax = "proto2";
-
-package securegcm;
-
-option optimize_for = LITE_RUNTIME;
-option java_package = "com.google.security.cryptauth.lib.securegcm";
-option java_outer_classname = "UkeyProto";
-
-message Ukey2Message {
- enum Type {
- UNKNOWN_DO_NOT_USE = 0;
- ALERT = 1;
- CLIENT_INIT = 2;
- SERVER_INIT = 3;
- CLIENT_FINISH = 4;
- }
-
- optional Type message_type = 1; // Identifies message type
- optional bytes message_data = 2; // Actual message, to be parsed according to
- // message_type
-}
-
-message Ukey2Alert {
- enum AlertType {
- // Framing errors
- BAD_MESSAGE = 1; // The message could not be deserialized
- BAD_MESSAGE_TYPE = 2; // message_type has an undefined value
- INCORRECT_MESSAGE = 3; // message_type received does not correspond to
- // expected type at this stage of the protocol
- BAD_MESSAGE_DATA = 4; // Could not deserialize message_data as per
- // value inmessage_type
-
- // ClientInit and ServerInit errors
- BAD_VERSION = 100; // version is invalid; server cannot find
- // suitable version to speak with client.
- BAD_RANDOM = 101; // Random data is missing or of incorrect
- // length
- BAD_HANDSHAKE_CIPHER = 102; // No suitable handshake ciphers were found
- BAD_NEXT_PROTOCOL = 103; // The next protocol is missing, unknown, or
- // unsupported
- BAD_PUBLIC_KEY = 104; // The public key could not be parsed
-
- // Other errors
- INTERNAL_ERROR = 200; // An internal error has occurred. error_message
- // may contain additional details for logging
- // and debugging.
- }
-
- optional AlertType type = 1;
- optional string error_message = 2;
-}
-
-enum Ukey2HandshakeCipher {
- RESERVED = 0;
- P256_SHA512 = 100; // NIST P-256 used for ECDH, SHA512 used for
- // commitment
- CURVE25519_SHA512 = 200; // Curve 25519 used for ECDH, SHA512 used for
- // commitment
-}
-
-message Ukey2ClientInit {
- optional int32 version = 1; // highest supported version for rollback
- // protection
- optional bytes random = 2; // random bytes for replay/reuse protection
-
- // One commitment (hash of ClientFinished containing public key) per supported
- // cipher
- message CipherCommitment {
- optional Ukey2HandshakeCipher handshake_cipher = 1;
- optional bytes commitment = 2;
- }
- repeated CipherCommitment cipher_commitments = 3;
-
- // Next protocol that the client wants to speak.
- optional string next_protocol = 4;
-}
-
-message Ukey2ServerInit {
- optional int32 version = 1; // highest supported version for rollback
- // protection
- optional bytes random = 2; // random bytes for replay/reuse protection
-
- // Selected Cipher and corresponding public key
- optional Ukey2HandshakeCipher handshake_cipher = 3;
- optional bytes public_key = 4;
-}
-
-message Ukey2ClientFinished {
- optional bytes public_key = 1; // public key matching selected handshake
- // cipher
-}