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