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 // The ukey2_shell binary is a command-line based wrapper, exercising the
16 // UKey2Handshake class. Its main use is to be run in a Java test, testing the
17 // compatibility of the Java and C++ implementations.
18 //
19 // This program can be run in two modes, initiator or responder (default is
20 // initiator):
21 // ukey2_shell --mode=initiator --verification_string_length=32
22 // ukey2_shell --mode=responder --verification_string_length=32
23 //
24 // In initiator mode, the program performs the initiator handshake, and in
25 // responder mode, it performs the responder handshake.
26 //
27 // After the handshake is done, the program establishes a secure connection and
28 // enters a loop in which it processes the following commands:
29 // * encrypt <payload>: encrypts the payload and prints it.
30 // * decrypt <message>: decrypts the message and prints the payload.
31 // * session_unique: prints the session unique value.
32 //
33 // IO is performed on stdin and stdout. To provide frame control, all frames
34 // will have the following simple format:
35 // [ length | bytes ]
36 // where |length| is a 4 byte big-endian encoded unsigned integer.
37 #include <cassert>
38 #include <cstdio>
39 #include <iostream>
40 #include <memory>
41
42 #include "securegcm/ukey2_handshake.h"
43 #include "absl/container/fixed_array.h"
44 #include "absl/flags/flag.h"
45 #include "absl/flags/parse.h"
46
47 #define LOG(ERROR) std::cerr
48 #define CHECK_EQ(a, b) do { if ((a) != (b)) abort(); } while(0)
49
50 ABSL_FLAG(
51 int, verification_string_length, 32,
52 "The length in bytes of the verification string. Must be a value between 1"
53 "and 32.");
54 ABSL_FLAG(string, mode, "initiator",
55 "The mode to run as: one of [initiator, responder]");
56
57 namespace securegcm {
58
59 namespace {
60
61 // Writes |message| to stdout in the frame format.
WriteFrame(const string & message)62 void WriteFrame(const string& message) {
63 // Write length of |message| in little-endian.
64 const uint32_t length = message.length();
65 fputc((length >> (3 * 8)) & 0xFF, stdout);
66 fputc((length >> (2 * 8)) & 0xFF, stdout);
67 fputc((length >> (1 * 8)) & 0xFF, stdout);
68 fputc((length >> (0 * 8)) & 0xFF, stdout);
69
70 // Write message to stdout.
71 CHECK_EQ(message.length(),
72 fwrite(message.c_str(), 1, message.length(), stdout));
73 CHECK_EQ(0, fflush(stdout));
74 }
75
76 // Returns a message read from stdin after parsing it from the frame format.
ReadFrame()77 string ReadFrame() {
78 // Read length of the frame from the stream.
79 uint8_t length_data[sizeof(uint32_t)];
80 CHECK_EQ(sizeof(uint32_t), fread(&length_data, 1, sizeof(uint32_t), stdin));
81
82 uint32_t length = 0;
83 length |= static_cast<uint32_t>(length_data[0]) << (3 * 8);
84 length |= static_cast<uint32_t>(length_data[1]) << (2 * 8);
85 length |= static_cast<uint32_t>(length_data[2]) << (1 * 8);
86 length |= static_cast<uint32_t>(length_data[3]) << (0 * 8);
87
88 // Read |length| bytes from the stream.
89 absl::FixedArray<char> buffer(length);
90 CHECK_EQ(length, fread(buffer.data(), 1, length, stdin));
91
92 return string(buffer.data(), length);
93 }
94
95 } // namespace
96
97 // Handles the runtime of the program in initiator or responder mode.
98 class UKey2Shell {
99 public:
100 explicit UKey2Shell(int verification_string_length);
101 ~UKey2Shell();
102
103 // Runs the shell, performing the initiator handshake for authentication.
104 bool RunAsInitiator();
105
106 // Runs the shell, performing the responder handshake for authentication.
107 bool RunAsResponder();
108
109 private:
110 // Writes the next handshake message obtained from |ukey2_handshake_| to
111 // stdout.
112 // If an error occurs, |tag| is logged.
113 bool WriteNextHandshakeMessage(const string& tag);
114
115 // Reads the next handshake message from stdin and parses it using
116 // |ukey2_handshake_|.
117 // If an error occurs, |tag| is logged.
118 bool ReadNextHandshakeMessage(const string& tag);
119
120 // Writes the verification string to stdout and waits for a confirmation from
121 // stdin.
122 bool ConfirmVerificationString();
123
124 // After authentication is completed, this function runs the loop handing the
125 // secure connection.
126 bool RunSecureConnectionLoop();
127
128 std::unique_ptr<UKey2Handshake> ukey2_handshake_;
129 const int verification_string_length_;
130 };
131
UKey2Shell(int verification_string_length)132 UKey2Shell::UKey2Shell(int verification_string_length)
133 : verification_string_length_(verification_string_length) {}
134
~UKey2Shell()135 UKey2Shell::~UKey2Shell() {}
136
WriteNextHandshakeMessage(const string & tag)137 bool UKey2Shell::WriteNextHandshakeMessage(const string& tag) {
138 const std::unique_ptr<string> message =
139 ukey2_handshake_->GetNextHandshakeMessage();
140 if (!message) {
141 LOG(ERROR) << "Failed to create [" << tag
142 << "] message: " << ukey2_handshake_->GetLastError();
143 return false;
144 }
145 WriteFrame(*message);
146 return true;
147 }
148
ReadNextHandshakeMessage(const string & tag)149 bool UKey2Shell::ReadNextHandshakeMessage(const string& tag) {
150 const string message = ReadFrame();
151 const UKey2Handshake::ParseResult result =
152 ukey2_handshake_->ParseHandshakeMessage(message);
153 if (!result.success) {
154 LOG(ERROR) << "Failed to parse [" << tag
155 << "] message: " << ukey2_handshake_->GetLastError();
156 if (result.alert_to_send) {
157 WriteFrame(*result.alert_to_send);
158 }
159 return false;
160 }
161 return true;
162 }
163
ConfirmVerificationString()164 bool UKey2Shell::ConfirmVerificationString() {
165 const std::unique_ptr<string> auth_string =
166 ukey2_handshake_->GetVerificationString(verification_string_length_);
167 if (!auth_string) {
168 LOG(ERROR) << "Failed to get verification string: "
169 << ukey2_handshake_->GetLastError();
170 return false;
171 }
172 WriteFrame(*auth_string);
173
174 // Wait for ack message.
175 const string message = ReadFrame();
176 if (message != "ok") {
177 LOG(ERROR) << "Expected string 'ok'";
178 return false;
179 }
180 ukey2_handshake_->VerifyHandshake();
181 return true;
182 }
183
RunSecureConnectionLoop()184 bool UKey2Shell::RunSecureConnectionLoop() {
185 const std::unique_ptr<D2DConnectionContextV1> connection_context =
186 ukey2_handshake_->ToConnectionContext();
187 if (!connection_context) {
188 LOG(ERROR) << "Failed to create connection context: "
189 << ukey2_handshake_->GetLastError();
190 return false;
191 }
192
193 for (;;) {
194 // Parse the next expression.
195 const string expression = ReadFrame();
196 const size_t pos = expression.find(" ");
197 if (pos == std::string::npos) {
198 LOG(ERROR) << "Invalid command in connection loop.";
199 return false;
200 }
201 const string command = expression.substr(0, pos);
202
203 if (command == "encrypt") {
204 const string payload = expression.substr(pos + 1, expression.length());
205 std::unique_ptr<string> encoded_message =
206 connection_context->EncodeMessageToPeer(payload);
207 if (!encoded_message) {
208 LOG(ERROR) << "Failed to encode payload of size " << payload.length();
209 return false;
210 }
211 WriteFrame(*encoded_message);
212 } else if (command == "decrypt") {
213 const string message = expression.substr(pos + 1, expression.length());
214 std::unique_ptr<string> decoded_payload =
215 connection_context->DecodeMessageFromPeer(message);
216 if (!decoded_payload) {
217 LOG(ERROR) << "Failed to decode message of size " << message.length();
218 return false;
219 }
220 WriteFrame(*decoded_payload);
221 } else if (command == "session_unique") {
222 std::unique_ptr<string> session_unique =
223 connection_context->GetSessionUnique();
224 if (!session_unique) {
225 LOG(ERROR) << "Failed to get session unique.";
226 return false;
227 }
228 WriteFrame(*session_unique);
229 } else {
230 LOG(ERROR) << "Unrecognized command: " << command;
231 return false;
232 }
233 }
234 }
235
RunAsInitiator()236 bool UKey2Shell::RunAsInitiator() {
237 ukey2_handshake_ = UKey2Handshake::ForInitiator(
238 UKey2Handshake::HandshakeCipher::P256_SHA512);
239 if (!ukey2_handshake_) {
240 LOG(ERROR) << "Unable to create UKey2Handshake";
241 return false;
242 }
243
244 // Perform handshake.
245 if (!WriteNextHandshakeMessage("Initiator Init")) return false;
246 if (!ReadNextHandshakeMessage("Responder Init")) return false;
247 if (!WriteNextHandshakeMessage("Initiator Finish")) return false;
248 if (!ConfirmVerificationString()) return false;
249
250 // Create a connection context.
251 return RunSecureConnectionLoop();
252 }
253
RunAsResponder()254 bool UKey2Shell::RunAsResponder() {
255 ukey2_handshake_ = UKey2Handshake::ForResponder(
256 UKey2Handshake::HandshakeCipher::P256_SHA512);
257 if (!ukey2_handshake_) {
258 LOG(ERROR) << "Unable to create UKey2Handshake";
259 return false;
260 }
261
262 // Perform handshake.
263 if (!ReadNextHandshakeMessage("Initiator Init")) return false;
264 if (!WriteNextHandshakeMessage("Responder Init")) return false;
265 if (!ReadNextHandshakeMessage("Initiator Finish")) return false;
266 if (!ConfirmVerificationString()) return false;
267
268 // Create a connection context.
269 return RunSecureConnectionLoop();
270 }
271
272 } // namespace securegcm
273
main(int argc,char ** argv)274 int main(int argc, char** argv) {
275 absl::ParseCommandLine(argc, argv);
276
277 const int verification_string_length =
278 absl::GetFlag(FLAGS_verification_string_length);
279 if (verification_string_length < 1 || verification_string_length > 32) {
280 LOG(ERROR) << "Invalid flag value, verification_string_length: "
281 << verification_string_length;
282 return 1;
283 }
284
285 securegcm::UKey2Shell shell(verification_string_length);
286 int exit_code = 0;
287 const string mode = absl::GetFlag(FLAGS_mode);
288 if (mode == "initiator") {
289 exit_code = !shell.RunAsInitiator();
290 } else if (mode == "responder") {
291 exit_code = !shell.RunAsResponder();
292 } else {
293 LOG(ERROR) << "Invalid flag value, mode: " << mode;
294 exit_code = 1;
295 }
296 return exit_code;
297 }
298