1 // Copyright 2020 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "securegcm/d2d_connection_context_v1.h"
16 
17 #include <limits>
18 #include <sstream>
19 
20 #include "proto/device_to_device_messages.pb.h"
21 #include "proto/securegcm.pb.h"
22 #include "securegcm/d2d_crypto_ops.h"
23 #include "securegcm/java_util.h"
24 #include "securemessage/secure_message_builder.h"
25 #include "securemessage/util.h"
26 
27 namespace securegcm {
28 
29 using securemessage::CryptoOps;
30 using securemessage::ByteBuffer;
31 using securemessage::Util;
32 
33 namespace {
34 
35 // Fields to fill in the GcmMetadata proto.
36 const Type kGcmMetadataType = DEVICE_TO_DEVICE_MESSAGE;
37 
38 // Represents the version of this context.
39 const uint8_t kProtocolVersion = 1;
40 
41 // The following represent the starting positions of the each entry within
42 // the string representation of this D2DConnectionContextV1.
43 //
44 // The saved session has a 1 byte protocol version, two 4 byte sequence numbers,
45 // and two 32 byte AES keys: (1 + 4 + 4 + 32 + 32 = 73).
46 
47 // The two sequence numbers are 4 bytes each.
48 const int kSequenceNumberLength = 4;
49 
50 // 32 byte AES keys.
51 const int kAesKeyLength = 32;
52 
53 // The encode sequence number starts at 1 to account for the 1 byte version
54 // number.
55 const int kEncodeSequenceStart = 1;
56 const int kEncodeSequenceEnd = kEncodeSequenceStart + kSequenceNumberLength;
57 
58 const int kDecodeSequenceStart = kEncodeSequenceEnd;
59 const int kDecodeSequenceEnd = kDecodeSequenceStart + kSequenceNumberLength;
60 
61 const int kEncodeKeyStart = kDecodeSequenceEnd;
62 const int kEncodeKeyEnd = kEncodeKeyStart + kAesKeyLength;
63 
64 const int kDecodeKeyStart = kEncodeKeyEnd;
65 const int kSavedSessionLength = kDecodeKeyStart + kAesKeyLength;
66 
67 // Convenience function to creates a DeviceToDeviceMessage proto with |payload|
68 // and |sequence_number|.
CreateDeviceToDeviceMessage(const std::string & payload,uint32_t sequence_number)69 DeviceToDeviceMessage CreateDeviceToDeviceMessage(const std::string& payload,
70                                                   uint32_t sequence_number) {
71   DeviceToDeviceMessage device_to_device_message;
72   device_to_device_message.set_sequence_number(sequence_number);
73   device_to_device_message.set_message(payload);
74   return device_to_device_message;
75 }
76 
77 // Convert 4 bytes in big-endian representation into an unsigned int.
BytesToUnsignedInt(std::vector<uint8_t> bytes)78 uint32_t BytesToUnsignedInt(std::vector<uint8_t> bytes) {
79   return bytes[0] << 24 | bytes[1] << 12 | bytes[2] << 8 | bytes[3];
80 }
81 
82 // Convert an unsigned int into a 4 byte big-endian representation.
UnsignedIntToBytes(uint32_t val)83 std::vector<uint8_t> UnsignedIntToBytes(uint32_t val) {
84   return {static_cast<uint8_t>(val >> 24), static_cast<uint8_t>(val >> 12),
85           static_cast<uint8_t>(val >> 8), static_cast<uint8_t>(val)};
86 }
87 
88 }  // namespace
89 
D2DConnectionContextV1(const CryptoOps::SecretKey & encode_key,const CryptoOps::SecretKey & decode_key,uint32_t encode_sequence_number,uint32_t decode_sequence_number)90 D2DConnectionContextV1::D2DConnectionContextV1(
91     const CryptoOps::SecretKey& encode_key,
92     const CryptoOps::SecretKey& decode_key, uint32_t encode_sequence_number,
93     uint32_t decode_sequence_number)
94     : encode_key_(encode_key),
95       decode_key_(decode_key),
96       encode_sequence_number_(encode_sequence_number),
97       decode_sequence_number_(decode_sequence_number) {}
98 
EncodeMessageToPeer(const std::string & payload)99 std::unique_ptr<std::string> D2DConnectionContextV1::EncodeMessageToPeer(
100     const std::string& payload) {
101   encode_sequence_number_++;
102   const DeviceToDeviceMessage message =
103       CreateDeviceToDeviceMessage(payload, encode_sequence_number_);
104 
105   const D2DCryptoOps::Payload payload_with_type(kGcmMetadataType,
106                                                 message.SerializeAsString());
107   return D2DCryptoOps::SigncryptPayload(payload_with_type, encode_key_);
108 }
109 
DecodeMessageFromPeer(const std::string & message)110 std::unique_ptr<std::string> D2DConnectionContextV1::DecodeMessageFromPeer(
111     const std::string& message) {
112   std::unique_ptr<D2DCryptoOps::Payload> payload =
113       D2DCryptoOps::VerifyDecryptPayload(message, decode_key_);
114   if (!payload) {
115     Util::LogError("DecodeMessageFromPeer: Failed to verify message.");
116     return nullptr;
117   }
118 
119   if (kGcmMetadataType != payload->type()) {
120     Util::LogError("DecodeMessageFromPeer: Wrong message type in D2D message.");
121     return nullptr;
122   }
123 
124   DeviceToDeviceMessage d2d_message;
125   if (!d2d_message.ParseFromString(payload->message())) {
126     Util::LogError("DecodeMessageFromPeer: Unable to parse D2D message proto.");
127     return nullptr;
128   }
129 
130   decode_sequence_number_++;
131   if (d2d_message.sequence_number() != decode_sequence_number_) {
132     std::ostringstream stream;
133     stream << "DecodeMessageFromPeer: Seqno in D2D message ("
134            << d2d_message.sequence_number()
135            << ") does not match expected seqno (" << decode_sequence_number_
136            << ").";
137     Util::LogError(stream.str());
138     return nullptr;
139   }
140 
141   return std::unique_ptr<std::string>(d2d_message.release_message());
142 }
143 
GetSessionUnique()144 std::unique_ptr<std::string> D2DConnectionContextV1::GetSessionUnique() {
145   const ByteBuffer encode_key_data = encode_key_.data();
146   const ByteBuffer decode_key_data = decode_key_.data();
147   const int32_t encode_key_hash = java_util::JavaHashCode(encode_key_data);
148   const int32_t decode_key_hash = java_util::JavaHashCode(decode_key_data);
149 
150   const ByteBuffer& first_buffer =
151       encode_key_hash < decode_key_hash ? encode_key_data : decode_key_data;
152   const ByteBuffer& second_buffer =
153       encode_key_hash < decode_key_hash ? decode_key_data : encode_key_data;
154 
155   ByteBuffer data_to_hash(D2DCryptoOps::kSalt, D2DCryptoOps::kSaltLength);
156   data_to_hash = ByteBuffer::Concat(data_to_hash, first_buffer);
157   data_to_hash = ByteBuffer::Concat(data_to_hash, second_buffer);
158 
159   std::unique_ptr<ByteBuffer> hash = CryptoOps::Sha256(data_to_hash);
160   if (!hash) {
161     Util::LogError("GetSessionUnique: SHA-256 hash failed.");
162     return nullptr;
163   }
164 
165   return std::unique_ptr<std::string>(new std::string(hash->String()));
166 }
167 
168 // Structure of saved session is:
169 //
170 // +---------------------------------------------------------------------------+
171 // | 1 Byte  |      4 Bytes      |      4 Bytes      |  32 Bytes  |  32 Bytes  |
172 // +---------------------------------------------------------------------------+
173 // | Version | encode seq number | decode seq number | encode key | decode key |
174 // +---------------------------------------------------------------------------+
175 //
176 // The sequence numbers are represented in big-endian.
SaveSession()177 std::unique_ptr<std::string> D2DConnectionContextV1::SaveSession() {
178   ByteBuffer byteBuffer = ByteBuffer(&kProtocolVersion, static_cast<size_t>(1));
179 
180   // Append encode sequence number.
181   std::vector<uint8_t> encode_sequence_number_bytes =
182       UnsignedIntToBytes(encode_sequence_number_);
183   for (int i = 0; i < encode_sequence_number_bytes.size(); i++) {
184     byteBuffer.Append(static_cast<size_t>(1), encode_sequence_number_bytes[i]);
185   }
186 
187   // Append decode sequence number.
188   std::vector<uint8_t> decode_sequence_number_bytes =
189       UnsignedIntToBytes(decode_sequence_number_);
190   for (int i = 0; i < decode_sequence_number_bytes.size(); i++) {
191     byteBuffer.Append(static_cast<size_t>(1), decode_sequence_number_bytes[i]);
192   }
193 
194   // Append encode key.
195   byteBuffer = ByteBuffer::Concat(byteBuffer, encode_key_.data());
196 
197   // Append decode key.
198   byteBuffer = ByteBuffer::Concat(byteBuffer, decode_key_.data());
199 
200   return std::unique_ptr<std::string>(new std::string(byteBuffer.String()));
201 }
202 
203 // static.
204 std::unique_ptr<D2DConnectionContextV1>
FromSavedSession(const std::string & savedSessionInfo)205 D2DConnectionContextV1::FromSavedSession(const std::string& savedSessionInfo) {
206   ByteBuffer byteBuffer = ByteBuffer(savedSessionInfo);
207 
208   if (byteBuffer.size() != kSavedSessionLength) {
209     return nullptr;
210   }
211 
212   uint32_t encode_sequence_number = BytesToUnsignedInt(
213       byteBuffer.SubArray(kEncodeSequenceStart, kEncodeSequenceEnd)->Vector());
214   uint32_t decode_sequence_number = BytesToUnsignedInt(
215       byteBuffer.SubArray(kDecodeSequenceStart, kDecodeSequenceEnd)->Vector());
216 
217   const CryptoOps::SecretKey encode_key =
218       CryptoOps::SecretKey(*byteBuffer.SubArray(kEncodeKeyStart, kAesKeyLength),
219                            CryptoOps::KeyAlgorithm::AES_256_KEY);
220   const CryptoOps::SecretKey decode_key =
221       CryptoOps::SecretKey(*byteBuffer.SubArray(kDecodeKeyStart, kAesKeyLength),
222                            CryptoOps::KeyAlgorithm::AES_256_KEY);
223 
224   return std::unique_ptr<D2DConnectionContextV1>(new D2DConnectionContextV1(
225       encode_key, decode_key, encode_sequence_number, decode_sequence_number));
226 }
227 
228 }  // namespace securegcm
229