• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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