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