1 // Copyright 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "cast/receiver/channel/device_auth_namespace_handler.h"
6 
7 #include <utility>
8 
9 #include "cast/common/certificate/testing/test_helpers.h"
10 #include "cast/common/channel/message_util.h"
11 #include "cast/common/channel/proto/cast_channel.pb.h"
12 #include "cast/common/channel/testing/fake_cast_socket.h"
13 #include "cast/common/channel/testing/mock_socket_error_handler.h"
14 #include "cast/common/channel/virtual_connection_router.h"
15 #include "cast/common/public/cast_socket.h"
16 #include "cast/receiver/channel/static_credentials.h"
17 #include "cast/receiver/channel/testing/device_auth_test_helpers.h"
18 #include "gmock/gmock.h"
19 #include "gtest/gtest.h"
20 #include "platform/test/paths.h"
21 #include "testing/util/read_file.h"
22 
23 namespace openscreen {
24 namespace cast {
25 namespace {
26 
27 using ::cast::channel::AuthResponse;
28 using ::cast::channel::CastMessage;
29 using ::cast::channel::DeviceAuthMessage;
30 using ::cast::channel::SignatureAlgorithm;
31 
32 using ::testing::_;
33 using ::testing::ElementsAreArray;
34 using ::testing::Invoke;
35 
GetSpecificTestDataPath()36 const std::string& GetSpecificTestDataPath() {
37   static std::string data_path = GetTestDataPath() + "cast/receiver/channel/";
38   return data_path;
39 }
40 
41 class DeviceAuthNamespaceHandlerTest : public ::testing::Test {
42  public:
SetUp()43   void SetUp() override {
44     socket_ = fake_cast_socket_pair_.socket.get();
45     router_.TakeSocket(&mock_error_handler_,
46                        std::move(fake_cast_socket_pair_.socket));
47     router_.AddHandlerForLocalId(kPlatformReceiverId, &auth_handler_);
48   }
49 
50  protected:
51   const std::string& data_path_{GetSpecificTestDataPath()};
52   FakeCastSocketPair fake_cast_socket_pair_;
53   MockSocketErrorHandler mock_error_handler_;
54   CastSocket* socket_;
55 
56   StaticCredentialsProvider creds_;
57   VirtualConnectionRouter router_;
58   DeviceAuthNamespaceHandler auth_handler_{&creds_};
59 };
60 
61 // The tests in this file use a pre-recorded AuthChallenge as input and a
62 // matching pre-recorded AuthResponse for verification.  This is to make it
63 // easier to keep sender and receiver code separate, because the code that would
64 // really generate an AuthChallenge and verify an AuthResponse is under
65 // //cast/sender.  The pre-recorded messages come from an integration test which
66 // _does_ properly call both sender and receiver sides, but can optionally
67 // record the messages for use in these unit tests.  That test is currently
68 // under //cast/test.  See //cast/test/README.md for more information.
69 //
70 // The tests generally follow this procedure:
71 //  1. Read a fake device certificate chain + TLS certificate from disk.
72 //  2. Read a pre-recorded CastMessage proto containing an AuthChallenge.
73 //  3. Send this CastMessage over a CastSocket to a DeviceAuthNamespaceHandler.
74 //  4. Catch the CastMessage response and check that it has an AuthResponse.
75 //  5. Check the AuthResponse against another pre-recorded protobuf.
76 
TEST_F(DeviceAuthNamespaceHandlerTest,AuthResponse)77 TEST_F(DeviceAuthNamespaceHandlerTest, AuthResponse) {
78   InitStaticCredentialsFromFiles(
79       &creds_, nullptr, nullptr, data_path_ + "device_key.pem",
80       data_path_ + "device_chain.pem", data_path_ + "device_tls.pem");
81 
82   // Send an auth challenge.  |auth_handler_| will automatically respond via
83   // |router_| and we will catch the result in |challenge_reply|.
84   CastMessage auth_challenge;
85   const std::string auth_challenge_string =
86       ReadEntireFileToString(data_path_ + "auth_challenge.pb");
87   ASSERT_TRUE(auth_challenge.ParseFromString(auth_challenge_string));
88 
89   CastMessage challenge_reply;
90   EXPECT_CALL(fake_cast_socket_pair_.mock_peer_client, OnMessage(_, _))
91       .WillOnce(
92           Invoke([&challenge_reply](CastSocket* socket, CastMessage message) {
93             challenge_reply = std::move(message);
94           }));
95   ASSERT_TRUE(
96       fake_cast_socket_pair_.peer_socket->Send(std::move(auth_challenge)).ok());
97 
98   const std::string auth_response_string =
99       ReadEntireFileToString(data_path_ + "auth_response.pb");
100   AuthResponse expected_auth_response;
101   ASSERT_TRUE(expected_auth_response.ParseFromString(auth_response_string));
102 
103   DeviceAuthMessage auth_message;
104   ASSERT_EQ(challenge_reply.payload_type(),
105             ::cast::channel::CastMessage_PayloadType_BINARY);
106   ASSERT_TRUE(auth_message.ParseFromString(challenge_reply.payload_binary()));
107   ASSERT_TRUE(auth_message.has_response());
108   ASSERT_FALSE(auth_message.has_challenge());
109   ASSERT_FALSE(auth_message.has_error());
110   const AuthResponse& auth_response = auth_message.response();
111 
112   EXPECT_EQ(expected_auth_response.signature(), auth_response.signature());
113   EXPECT_EQ(expected_auth_response.client_auth_certificate(),
114             auth_response.client_auth_certificate());
115   EXPECT_EQ(expected_auth_response.signature_algorithm(),
116             auth_response.signature_algorithm());
117   EXPECT_EQ(expected_auth_response.sender_nonce(),
118             auth_response.sender_nonce());
119   EXPECT_EQ(expected_auth_response.hash_algorithm(),
120             auth_response.hash_algorithm());
121   EXPECT_EQ(expected_auth_response.crl(), auth_response.crl());
122   EXPECT_THAT(
123       auth_response.intermediate_certificate(),
124       ElementsAreArray(expected_auth_response.intermediate_certificate()));
125 }
126 
TEST_F(DeviceAuthNamespaceHandlerTest,BadNonce)127 TEST_F(DeviceAuthNamespaceHandlerTest, BadNonce) {
128   InitStaticCredentialsFromFiles(
129       &creds_, nullptr, nullptr, data_path_ + "device_key.pem",
130       data_path_ + "device_chain.pem", data_path_ + "device_tls.pem");
131 
132   // Send an auth challenge.  |auth_handler_| will automatically respond via
133   // |router_| and we will catch the result in |challenge_reply|.
134   CastMessage auth_challenge;
135   const std::string auth_challenge_string =
136       ReadEntireFileToString(data_path_ + "auth_challenge.pb");
137   ASSERT_TRUE(auth_challenge.ParseFromString(auth_challenge_string));
138 
139   // Change the nonce to be different from what was used to record the correct
140   // response originally.
141   DeviceAuthMessage msg;
142   ASSERT_EQ(auth_challenge.payload_type(),
143             ::cast::channel::CastMessage_PayloadType_BINARY);
144   ASSERT_TRUE(msg.ParseFromString(auth_challenge.payload_binary()));
145   ASSERT_TRUE(msg.has_challenge());
146   std::string* nonce = msg.mutable_challenge()->mutable_sender_nonce();
147   (*nonce)[0] = ~(*nonce)[0];
148   std::string new_payload;
149   ASSERT_TRUE(msg.SerializeToString(&new_payload));
150   auth_challenge.set_payload_binary(new_payload);
151 
152   CastMessage challenge_reply;
153   EXPECT_CALL(fake_cast_socket_pair_.mock_peer_client, OnMessage(_, _))
154       .WillOnce(
155           Invoke([&challenge_reply](CastSocket* socket, CastMessage message) {
156             challenge_reply = std::move(message);
157           }));
158   ASSERT_TRUE(
159       fake_cast_socket_pair_.peer_socket->Send(std::move(auth_challenge)).ok());
160 
161   const std::string auth_response_string =
162       ReadEntireFileToString(data_path_ + "auth_response.pb");
163   AuthResponse expected_auth_response;
164   ASSERT_TRUE(expected_auth_response.ParseFromString(auth_response_string));
165 
166   DeviceAuthMessage auth_message;
167   ASSERT_EQ(challenge_reply.payload_type(),
168             ::cast::channel::CastMessage_PayloadType_BINARY);
169   ASSERT_TRUE(auth_message.ParseFromString(challenge_reply.payload_binary()));
170   ASSERT_TRUE(auth_message.has_response());
171   ASSERT_FALSE(auth_message.has_challenge());
172   ASSERT_FALSE(auth_message.has_error());
173   const AuthResponse& auth_response = auth_message.response();
174 
175   // NOTE: This is the ultimate result of the nonce-mismatch.
176   EXPECT_NE(expected_auth_response.signature(), auth_response.signature());
177 }
178 
TEST_F(DeviceAuthNamespaceHandlerTest,UnsupportedSignatureAlgorithm)179 TEST_F(DeviceAuthNamespaceHandlerTest, UnsupportedSignatureAlgorithm) {
180   InitStaticCredentialsFromFiles(
181       &creds_, nullptr, nullptr, data_path_ + "device_key.pem",
182       data_path_ + "device_chain.pem", data_path_ + "device_tls.pem");
183 
184   // Send an auth challenge.  |auth_handler_| will automatically respond via
185   // |router_| and we will catch the result in |challenge_reply|.
186   CastMessage auth_challenge;
187   const std::string auth_challenge_string =
188       ReadEntireFileToString(data_path_ + "auth_challenge.pb");
189   ASSERT_TRUE(auth_challenge.ParseFromString(auth_challenge_string));
190 
191   // Change the signature algorithm an unsupported value.
192   DeviceAuthMessage msg;
193   ASSERT_EQ(auth_challenge.payload_type(),
194             ::cast::channel::CastMessage_PayloadType_BINARY);
195   ASSERT_TRUE(msg.ParseFromString(auth_challenge.payload_binary()));
196   ASSERT_TRUE(msg.has_challenge());
197   msg.mutable_challenge()->set_signature_algorithm(
198       SignatureAlgorithm::RSASSA_PSS);
199   std::string new_payload;
200   ASSERT_TRUE(msg.SerializeToString(&new_payload));
201   auth_challenge.set_payload_binary(new_payload);
202 
203   CastMessage challenge_reply;
204   EXPECT_CALL(fake_cast_socket_pair_.mock_peer_client, OnMessage(_, _))
205       .WillOnce(
206           Invoke([&challenge_reply](CastSocket* socket, CastMessage message) {
207             challenge_reply = std::move(message);
208           }));
209   ASSERT_TRUE(
210       fake_cast_socket_pair_.peer_socket->Send(std::move(auth_challenge)).ok());
211 
212   DeviceAuthMessage auth_message;
213   ASSERT_EQ(challenge_reply.payload_type(),
214             ::cast::channel::CastMessage_PayloadType_BINARY);
215   ASSERT_TRUE(auth_message.ParseFromString(challenge_reply.payload_binary()));
216   ASSERT_FALSE(auth_message.has_response());
217   ASSERT_FALSE(auth_message.has_challenge());
218   ASSERT_TRUE(auth_message.has_error());
219 }
220 
221 }  // namespace
222 }  // namespace cast
223 }  // namespace openscreen
224