blob: 8a9a6123b75cd5b25691f36d390e387c9b2c7e4b [file] [log] [blame]
// 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