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