1 /*
2 * Copyright (c) 2018-present, Facebook, Inc.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of this source tree.
7 */
8
9 #include <folly/portability/GMock.h>
10 #include <folly/portability/GTest.h>
11
12 #include <fizz/server/AeadTicketCipher.h>
13
14 #include <fizz/crypto/aead/test/Mocks.h>
15 #include <fizz/crypto/test/Mocks.h>
16 #include <fizz/crypto/test/TestUtil.h>
17 #include <fizz/protocol/clock/test/Mocks.h>
18 #include <folly/String.h>
19
20 using namespace fizz::test;
21 using namespace folly;
22 using namespace testing;
23
24 static constexpr StringPiece ticketSecret1{
25 "90a791cf38c0b5c20447ef029ae1bc4bf3eecc2e85042174497671835ceaccd9"};
26 // secret: 13deec41c45b2f1c4f595ad5972d13047fba09031ba53140c751380e74114cc4
27 // salt: 4444444444444444444444444444444444444444444444444444444444444444
28 // hkdf output: c951156f3dcb1ab243a3f2c8e4346bec92cb25d241ae821484081388
29 static constexpr StringPiece ticket1{
30 "444444444444444444444444444444444444444444444444444444444444444400000000579bb5b10c83d7a581f6b8f7bd25acde3dabfe6f59e5147bde86681831"};
31 static constexpr StringPiece ticket2{
32 "444444444444444444444444444444444444444444444444444444444444444400000001f444b4f0a0d1dd8b26d3a0afa275b4f6956cfdce4857f9ec46177d0ff9"};
33
34 static constexpr StringPiece ticketSecret2{
35 "04de0343a34c12f17f8b9696443d55e533ca1eef92bdba6634a46b604e51436d"};
36 // secret: d2c07e1107d3024bd08ebf34d59b9726d05bd7082da80cbb1e90b879e0770b5f
37 // salt: 5cef31d266ca1fe1d634de9b95668d3d8895d4837d3ba81787185ff51c056e95
38 // hkdf output: f7d80b07236875b5a48bdc5bd4642a775c05c231b9507285675c1e0b
39 static constexpr StringPiece ticket3{
40 "5cef31d266ca1fe1d634de9b95668d3d8895d4837d3ba81787185ff51c056e95000000005d19a72a3becb5b063346fdf1ec6f9d9d4ddd82cb5f34a8ba0d19e4b69"};
41
42 // Uses context 'foobar'
43 static constexpr StringPiece ticket4{
44 "5cef31d266ca1fe1d634de9b95668d3d8895d4837d3ba81787185ff51c056e95000000005b2168cc0fda4f9987b5e9d045845ba4809ac5189158c578c0e5d11b00"};
45
46 static constexpr StringPiece badTicket{
47 "5d19a72a3becb5b061346fdf1ec6f9d9d4ddd82cb5f34a8ba0d19e4b69"};
48
49 namespace fizz {
50 namespace server {
51 namespace test {
52
53 class MockTicketCodecInstance {
54 public:
55 MOCK_CONST_METHOD1(_encode, Buf(ResumptionState& state));
56 MOCK_CONST_METHOD3(
57 _decode,
58 ResumptionState(
59 Buf& encoded,
60 const Factory& factory,
61 const CertManager& certManager));
62 };
63
64 class MockTicketCodec {
65 public:
66 static constexpr folly::StringPiece Label{"Mock Ticket Codec"};
encode(ResumptionState state)67 static Buf encode(ResumptionState state) {
68 return instance->_encode(state);
69 }
70 static ResumptionState
decode(Buf encoded,const Factory & factory,const CertManager & certManager)71 decode(Buf encoded, const Factory& factory, const CertManager& certManager) {
72 return instance->_decode(encoded, factory, certManager);
73 }
74 static MockTicketCodecInstance* instance;
75 };
76 MockTicketCodecInstance* MockTicketCodec::instance;
77 constexpr folly::StringPiece MockTicketCodec::Label;
78
79 using TestAeadTicketCipher = Aead128GCMTicketCipher<MockTicketCodec>;
80
81 class AeadTicketCipherTest : public Test {
82 public:
AeadTicketCipherTest()83 AeadTicketCipherTest()
84 : cipher_(
85 std::make_shared<OpenSSLFactory>(),
86 std::make_shared<CertManager>()) {}
87
88 ~AeadTicketCipherTest() override = default;
SetUp()89 void SetUp() override {
90 MockTicketCodec::instance = &codec_;
91 clock_ = std::make_shared<MockClock>();
92 policy_.setClock(clock_);
93 cipher_.setPolicy(policy_);
94 }
95
96 protected:
97 TicketPolicy policy_;
98 TestAeadTicketCipher cipher_;
99 MockTicketCodecInstance codec_;
100 std::shared_ptr<MockClock> clock_;
101 std::shared_ptr<Factory> factory_;
102 std::shared_ptr<CertManager> certManager_;
103
rebuildCipher(std::string pskContext="")104 void rebuildCipher(std::string pskContext = "") {
105 if (!pskContext.empty()) {
106 cipher_ = TestAeadTicketCipher(
107 std::make_shared<OpenSSLFactory>(),
108 std::make_shared<CertManager>(),
109 pskContext);
110 } else {
111 cipher_ = TestAeadTicketCipher(
112 std::make_shared<OpenSSLFactory>(), std::make_shared<CertManager>());
113 }
114 cipher_.setPolicy(policy_);
115 auto s1 = toIOBuf(ticketSecret1);
116 auto s2 = toIOBuf(ticketSecret2);
117 std::vector<ByteRange> ticketSecrets{{s1->coalesce(), s2->coalesce()}};
118 EXPECT_TRUE(cipher_.setTicketSecrets(std::move(ticketSecrets)));
119 }
120
expectDecode()121 void expectDecode() {
122 EXPECT_CALL(codec_, _decode(_, _, _))
123 .WillOnce(Invoke([](Buf& encoded,
124 const Factory& /* factory */,
125 const CertManager& /* certManager */) {
126 EXPECT_TRUE(
127 IOBufEqualTo()(encoded, IOBuf::copyBuffer("encodedticket")));
128 return ResumptionState();
129 }));
130 }
131
checkUnsetEncrypt()132 void checkUnsetEncrypt() {
133 ResumptionState state;
134 EXPECT_FALSE(cipher_.encrypt(std::move(state)).get().has_value());
135 }
136
updatePolicy(std::chrono::seconds ticketValidity,folly::Optional<std::chrono::seconds> handshakeValidity=folly::none)137 void updatePolicy(
138 std::chrono::seconds ticketValidity,
139 folly::Optional<std::chrono::seconds> handshakeValidity = folly::none) {
140 policy_.setTicketValidity(ticketValidity);
141 if (handshakeValidity.has_value()) {
142 policy_.setHandshakeValidity(*handshakeValidity);
143 }
144 }
145 };
146
TEST_F(AeadTicketCipherTest,TestEncryptNoTicketSecrets)147 TEST_F(AeadTicketCipherTest, TestEncryptNoTicketSecrets) {
148 checkUnsetEncrypt();
149 }
150
TEST_F(AeadTicketCipherTest,TestEncrypt)151 TEST_F(AeadTicketCipherTest, TestEncrypt) {
152 useMockRandom();
153 updatePolicy(std::chrono::seconds(5));
154 rebuildCipher();
155 EXPECT_CALL(codec_, _encode(_)).WillOnce(InvokeWithoutArgs([]() {
156 return IOBuf::copyBuffer("encodedticket");
157 }));
158 ResumptionState state;
159 auto result = cipher_.encrypt(std::move(state)).get();
160 EXPECT_TRUE(result.has_value());
161 EXPECT_TRUE(IOBufEqualTo()(result->first, toIOBuf(ticket1)));
162 EXPECT_EQ(result->second, std::chrono::seconds(5));
163 }
164
TEST_F(AeadTicketCipherTest,TestHandshakeExpiration)165 TEST_F(AeadTicketCipherTest, TestHandshakeExpiration) {
166 useMockRandom();
167 updatePolicy(std::chrono::seconds(2), std::chrono::seconds(4));
168 rebuildCipher();
169 auto time = std::chrono::system_clock::now();
170 EXPECT_CALL(*clock_, getCurrentTime()).WillOnce(Return(time));
171
172 EXPECT_CALL(codec_, _encode(_)).WillOnce(InvokeWithoutArgs([]() {
173 return IOBuf::copyBuffer("encodedticket");
174 }));
175 EXPECT_CALL(codec_, _decode(_, _, _))
176 .Times(2)
177 .WillRepeatedly(InvokeWithoutArgs([time]() {
178 ResumptionState res;
179 res.handshakeTime = time;
180 return res;
181 }));
182 ResumptionState state;
183 state.handshakeTime = time;
184 auto result = cipher_.encrypt(std::move(state)).get();
185 EXPECT_TRUE(result.has_value());
186 EXPECT_TRUE(IOBufEqualTo()(result->first, toIOBuf(ticket1)));
187 EXPECT_EQ(result->second, std::chrono::seconds(2));
188 EXPECT_CALL(*clock_, getCurrentTime())
189 .WillOnce(Return(time + std::chrono::seconds(1)));
190 auto decResult = cipher_.decrypt(result->first->clone()).get();
191 EXPECT_EQ(decResult.first, PskType::Resumption);
192 EXPECT_TRUE(decResult.second.has_value());
193 EXPECT_CALL(*clock_, getCurrentTime())
194 .WillOnce(Return(time + std::chrono::seconds(5)));
195 auto badResult = cipher_.decrypt(result->first->clone()).get();
196 EXPECT_EQ(badResult.first, PskType::Rejected);
197 EXPECT_FALSE(badResult.second.has_value());
198 }
199
TEST_F(AeadTicketCipherTest,TestTicketLifetime)200 TEST_F(AeadTicketCipherTest, TestTicketLifetime) {
201 useMockRandom();
202 updatePolicy(std::chrono::seconds(2), std::chrono::seconds(4));
203 rebuildCipher();
204 auto time = std::chrono::system_clock::now();
205
206 EXPECT_CALL(codec_, _encode(_))
207 .Times(2)
208 .WillRepeatedly(InvokeWithoutArgs(
209 []() { return IOBuf::copyBuffer("encodedticket"); }));
210
211 // At handshake time, expect ticket validity.
212 EXPECT_CALL(*clock_, getCurrentTime()).WillOnce(Return(time));
213 ResumptionState state;
214 state.handshakeTime = time;
215 auto result = cipher_.encrypt(std::move(state)).get();
216 EXPECT_TRUE(result.has_value());
217 EXPECT_TRUE(IOBufEqualTo()(result->first, toIOBuf(ticket1)));
218 EXPECT_EQ(result->second, std::chrono::seconds(2));
219
220 // At 3 seconds in, expect 1 second (remaining handshake validity)
221 EXPECT_CALL(*clock_, getCurrentTime())
222 .WillOnce(Return(time + std::chrono::seconds(3)));
223 auto result2 = cipher_.encrypt(std::move(state)).get();
224 EXPECT_TRUE(result2.has_value());
225 EXPECT_TRUE(IOBufEqualTo()(result2->first, toIOBuf(ticket1)));
226 EXPECT_EQ(result2->second, std::chrono::seconds(1));
227
228 // 5 seconds in, no longer valid. Expect none.
229 EXPECT_CALL(*clock_, getCurrentTime())
230 .WillOnce(Return(time + std::chrono::seconds(5)));
231 auto result3 = cipher_.encrypt(std::move(state)).get();
232 EXPECT_FALSE(result3.has_value());
233 }
234
TEST_F(AeadTicketCipherTest,TestEncryptExpiredHandshakeTicket)235 TEST_F(AeadTicketCipherTest, TestEncryptExpiredHandshakeTicket) {
236 useMockRandom();
237 updatePolicy(std::chrono::hours(1), std::chrono::seconds(4));
238 rebuildCipher();
239 auto time = std::chrono::system_clock::now();
240 EXPECT_CALL(*clock_, getCurrentTime()).WillOnce(Return(time));
241
242 ResumptionState state;
243 state.handshakeTime = time - std::chrono::seconds(5);
244 auto result = cipher_.encrypt(std::move(state)).get();
245 EXPECT_FALSE(result.has_value());
246 }
247
TEST_F(AeadTicketCipherTest,TestEncryptTicketFromFuture)248 TEST_F(AeadTicketCipherTest, TestEncryptTicketFromFuture) {
249 useMockRandom();
250 updatePolicy(std::chrono::seconds(2), std::chrono::seconds(4));
251 rebuildCipher();
252 auto time = std::chrono::system_clock::now();
253 EXPECT_CALL(*clock_, getCurrentTime()).WillOnce(Return(time));
254
255 EXPECT_CALL(codec_, _encode(_)).WillOnce(InvokeWithoutArgs([]() {
256 return IOBuf::copyBuffer("encodedticket");
257 }));
258 ResumptionState state;
259 // Ticket was created in the future. Validity period should be equal
260 // to maximum (as we can't be sure how old it really is)
261 state.handshakeTime = time + std::chrono::seconds(5);
262 auto result = cipher_.encrypt(std::move(state)).get();
263 EXPECT_TRUE(result.has_value());
264 EXPECT_TRUE(IOBufEqualTo()(result->first, toIOBuf(ticket1)));
265 EXPECT_EQ(result->second, std::chrono::seconds(2));
266 }
267
TEST_F(AeadTicketCipherTest,TestDecryptNoTicketSecrets)268 TEST_F(AeadTicketCipherTest, TestDecryptNoTicketSecrets) {
269 auto result = cipher_.decrypt(toIOBuf(ticket1)).get();
270 EXPECT_EQ(result.first, PskType::Rejected);
271 EXPECT_FALSE(result.second.has_value());
272 }
273
TEST_F(AeadTicketCipherTest,TestDecryptFirst)274 TEST_F(AeadTicketCipherTest, TestDecryptFirst) {
275 rebuildCipher();
276 expectDecode();
277 auto result = cipher_.decrypt(toIOBuf(ticket1)).get();
278 EXPECT_EQ(result.first, PskType::Resumption);
279 EXPECT_TRUE(result.second.has_value());
280 }
281
TEST_F(AeadTicketCipherTest,TestDecryptSecond)282 TEST_F(AeadTicketCipherTest, TestDecryptSecond) {
283 rebuildCipher();
284 expectDecode();
285 auto result = cipher_.decrypt(toIOBuf(ticket3)).get();
286 EXPECT_EQ(result.first, PskType::Resumption);
287 EXPECT_TRUE(result.second.has_value());
288 }
289
TEST_F(AeadTicketCipherTest,TestDecryptWithContext)290 TEST_F(AeadTicketCipherTest, TestDecryptWithContext) {
291 rebuildCipher("foobar");
292 expectDecode();
293 auto result = cipher_.decrypt(toIOBuf(ticket4)).get();
294 EXPECT_EQ(result.first, PskType::Resumption);
295 EXPECT_TRUE(result.second.has_value());
296 }
297
TEST_F(AeadTicketCipherTest,TestDecryptWithoutContext)298 TEST_F(AeadTicketCipherTest, TestDecryptWithoutContext) {
299 rebuildCipher();
300 // Ticket 4 needs context 'foobar'
301 auto result = cipher_.decrypt(toIOBuf(ticket4)).get();
302 EXPECT_EQ(result.first, PskType::Rejected);
303 EXPECT_FALSE(result.second.has_value());
304 }
305
TEST_F(AeadTicketCipherTest,TestDecryptWithWrongContext)306 TEST_F(AeadTicketCipherTest, TestDecryptWithWrongContext) {
307 rebuildCipher("barbaz");
308 // barbaz =/= foobar
309 auto result = cipher_.decrypt(toIOBuf(ticket4)).get();
310 EXPECT_EQ(result.first, PskType::Rejected);
311 EXPECT_FALSE(result.second.has_value());
312 }
313
TEST_F(AeadTicketCipherTest,TestDecryptWithUnneededContext)314 TEST_F(AeadTicketCipherTest, TestDecryptWithUnneededContext) {
315 rebuildCipher("foobar");
316 // Now test that ticket 3 with context 'foobar' doesn't work
317 auto result = cipher_.decrypt(toIOBuf(ticket3)).get();
318 EXPECT_EQ(result.first, PskType::Rejected);
319 EXPECT_FALSE(result.second.has_value());
320 }
321
TEST_F(AeadTicketCipherTest,TestDecryptSeqNum)322 TEST_F(AeadTicketCipherTest, TestDecryptSeqNum) {
323 rebuildCipher();
324 expectDecode();
325 auto result = cipher_.decrypt(toIOBuf(ticket2)).get();
326 EXPECT_EQ(result.first, PskType::Resumption);
327 EXPECT_TRUE(result.second.has_value());
328 }
329
TEST_F(AeadTicketCipherTest,TestDecryptFailed)330 TEST_F(AeadTicketCipherTest, TestDecryptFailed) {
331 rebuildCipher();
332 auto result = cipher_.decrypt(toIOBuf(badTicket)).get();
333 EXPECT_EQ(result.first, PskType::Rejected);
334 EXPECT_FALSE(result.second.has_value());
335 }
336
TEST_F(AeadTicketCipherTest,TestDecryptTooShort)337 TEST_F(AeadTicketCipherTest, TestDecryptTooShort) {
338 rebuildCipher();
339 auto result = cipher_.decrypt(IOBuf::copyBuffer("short")).get();
340 EXPECT_EQ(result.first, PskType::Rejected);
341 EXPECT_FALSE(result.second.has_value());
342 }
343
TEST_F(AeadTicketCipherTest,TestUnsetTicketSecrets)344 TEST_F(AeadTicketCipherTest, TestUnsetTicketSecrets) {
345 rebuildCipher();
346 EXPECT_TRUE(cipher_.setTicketSecrets(std::vector<ByteRange>()));
347 checkUnsetEncrypt();
348 }
349
TEST_F(AeadTicketCipherTest,TestSetTicketSecretsTooShort)350 TEST_F(AeadTicketCipherTest, TestSetTicketSecretsTooShort) {
351 StringPiece tooShort{"short"};
352 std::vector<ByteRange> ticketSecrets{{tooShort}};
353 EXPECT_FALSE(cipher_.setTicketSecrets(std::move(ticketSecrets)));
354 checkUnsetEncrypt();
355 }
356 } // namespace test
357 } // namespace server
358 } // namespace fizz
359