1 /*
2  *
3  * Copyright 2018 gRPC authors.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  *
17  */
18 
19 #include <grpc/support/port_platform.h>
20 
21 #include "src/core/tsi/alts/zero_copy_frame_protector/alts_iovec_record_protocol.h"
22 
23 #include <stdlib.h>
24 #include <string.h>
25 
26 #include <grpc/support/alloc.h>
27 #include <grpc/support/log.h>
28 
29 #include "src/core/tsi/alts/frame_protector/alts_counter.h"
30 
31 struct alts_iovec_record_protocol {
32   alts_counter* ctr;
33   gsec_aead_crypter* crypter;
34   size_t tag_length;
35   bool is_integrity_only;
36   bool is_protect;
37 };
38 
39 /* Copies error message to destination.  */
maybe_copy_error_msg(const char * src,char ** dst)40 static void maybe_copy_error_msg(const char* src, char** dst) {
41   if (dst != nullptr && src != nullptr) {
42     *dst = static_cast<char*>(gpr_malloc(strlen(src) + 1));
43     memcpy(*dst, src, strlen(src) + 1);
44   }
45 }
46 
47 /* Appends error message to destination.  */
maybe_append_error_msg(const char * appendix,char ** dst)48 static void maybe_append_error_msg(const char* appendix, char** dst) {
49   if (dst != nullptr && appendix != nullptr) {
50     int dst_len = static_cast<int>(strlen(*dst));
51     *dst = static_cast<char*>(realloc(*dst, dst_len + strlen(appendix) + 1));
52     assert(*dst != nullptr);
53     memcpy(*dst + dst_len, appendix, strlen(appendix) + 1);
54   }
55 }
56 
57 /* Use little endian to interpret a string of bytes as uint32_t.  */
load_32_le(const unsigned char * buffer)58 static uint32_t load_32_le(const unsigned char* buffer) {
59   return (static_cast<uint32_t>(buffer[3]) << 24) |
60          (static_cast<uint32_t>(buffer[2]) << 16) |
61          (static_cast<uint32_t>(buffer[1]) << 8) |
62          static_cast<uint32_t>(buffer[0]);
63 }
64 
65 /* Store uint32_t as a string of little endian bytes.  */
store_32_le(uint32_t value,unsigned char * buffer)66 static void store_32_le(uint32_t value, unsigned char* buffer) {
67   buffer[3] = static_cast<unsigned char>(value >> 24) & 0xFF;
68   buffer[2] = static_cast<unsigned char>(value >> 16) & 0xFF;
69   buffer[1] = static_cast<unsigned char>(value >> 8) & 0xFF;
70   buffer[0] = static_cast<unsigned char>(value) & 0xFF;
71 }
72 
73 /* Ensures header and tag iovec have sufficient length.  */
ensure_header_and_tag_length(const alts_iovec_record_protocol * rp,iovec_t header,iovec_t tag,char ** error_details)74 static grpc_status_code ensure_header_and_tag_length(
75     const alts_iovec_record_protocol* rp, iovec_t header, iovec_t tag,
76     char** error_details) {
77   if (rp == nullptr) {
78     return GRPC_STATUS_FAILED_PRECONDITION;
79   }
80   if (header.iov_base == nullptr) {
81     maybe_copy_error_msg("Header is nullptr.", error_details);
82     return GRPC_STATUS_INVALID_ARGUMENT;
83   }
84   if (header.iov_len != alts_iovec_record_protocol_get_header_length()) {
85     maybe_copy_error_msg("Header length is incorrect.", error_details);
86     return GRPC_STATUS_INVALID_ARGUMENT;
87   }
88   if (tag.iov_base == nullptr) {
89     maybe_copy_error_msg("Tag is nullptr.", error_details);
90     return GRPC_STATUS_INVALID_ARGUMENT;
91   }
92   if (tag.iov_len != rp->tag_length) {
93     maybe_copy_error_msg("Tag length is incorrect.", error_details);
94     return GRPC_STATUS_INVALID_ARGUMENT;
95   }
96   return GRPC_STATUS_OK;
97 }
98 
99 /* Increments crypter counter and checks overflow.  */
increment_counter(alts_counter * counter,char ** error_details)100 static grpc_status_code increment_counter(alts_counter* counter,
101                                           char** error_details) {
102   if (counter == nullptr) {
103     return GRPC_STATUS_FAILED_PRECONDITION;
104   }
105   bool is_overflow = false;
106   grpc_status_code status =
107       alts_counter_increment(counter, &is_overflow, error_details);
108   if (status != GRPC_STATUS_OK) {
109     return status;
110   }
111   if (is_overflow) {
112     maybe_copy_error_msg("Crypter counter is overflowed.", error_details);
113     return GRPC_STATUS_INTERNAL;
114   }
115   return GRPC_STATUS_OK;
116 }
117 
118 /* Given an array of iovec, computes the total length of buffer.  */
get_total_length(const iovec_t * vec,size_t vec_length)119 static size_t get_total_length(const iovec_t* vec, size_t vec_length) {
120   size_t total_length = 0;
121   for (size_t i = 0; i < vec_length; ++i) {
122     total_length += vec[i].iov_len;
123   }
124   return total_length;
125 }
126 
127 /* Writes frame header given data and tag length.  */
write_frame_header(size_t data_length,unsigned char * header,char ** error_details)128 static grpc_status_code write_frame_header(size_t data_length,
129                                            unsigned char* header,
130                                            char** error_details) {
131   if (header == nullptr) {
132     maybe_copy_error_msg("Header is nullptr.", error_details);
133     return GRPC_STATUS_FAILED_PRECONDITION;
134   }
135   size_t frame_length = kZeroCopyFrameMessageTypeFieldSize + data_length;
136   store_32_le(static_cast<uint32_t>(frame_length), header);
137   store_32_le(kZeroCopyFrameMessageType,
138               header + kZeroCopyFrameLengthFieldSize);
139   return GRPC_STATUS_OK;
140 }
141 
142 /* Verifies frame header given protected data length.  */
verify_frame_header(size_t data_length,unsigned char * header,char ** error_details)143 static grpc_status_code verify_frame_header(size_t data_length,
144                                             unsigned char* header,
145                                             char** error_details) {
146   if (header == nullptr) {
147     maybe_copy_error_msg("Header is nullptr.", error_details);
148     return GRPC_STATUS_FAILED_PRECONDITION;
149   }
150   size_t frame_length = load_32_le(header);
151   if (frame_length != kZeroCopyFrameMessageTypeFieldSize + data_length) {
152     maybe_copy_error_msg("Bad frame length.", error_details);
153     return GRPC_STATUS_INTERNAL;
154   }
155   size_t message_type = load_32_le(header + kZeroCopyFrameLengthFieldSize);
156   if (message_type != kZeroCopyFrameMessageType) {
157     maybe_copy_error_msg("Unsupported message type.", error_details);
158     return GRPC_STATUS_INTERNAL;
159   }
160   return GRPC_STATUS_OK;
161 }
162 
163 /* --- alts_iovec_record_protocol methods implementation. --- */
164 
alts_iovec_record_protocol_get_header_length()165 size_t alts_iovec_record_protocol_get_header_length() {
166   return kZeroCopyFrameHeaderSize;
167 }
168 
alts_iovec_record_protocol_get_tag_length(const alts_iovec_record_protocol * rp)169 size_t alts_iovec_record_protocol_get_tag_length(
170     const alts_iovec_record_protocol* rp) {
171   if (rp != nullptr) {
172     return rp->tag_length;
173   }
174   return 0;
175 }
176 
alts_iovec_record_protocol_max_unprotected_data_size(const alts_iovec_record_protocol * rp,size_t max_protected_frame_size)177 size_t alts_iovec_record_protocol_max_unprotected_data_size(
178     const alts_iovec_record_protocol* rp, size_t max_protected_frame_size) {
179   if (rp == nullptr) {
180     return 0;
181   }
182   size_t overhead_bytes_size =
183       kZeroCopyFrameMessageTypeFieldSize + rp->tag_length;
184   if (max_protected_frame_size <= overhead_bytes_size) return 0;
185   return max_protected_frame_size - overhead_bytes_size;
186 }
187 
alts_iovec_record_protocol_integrity_only_protect(alts_iovec_record_protocol * rp,const iovec_t * unprotected_vec,size_t unprotected_vec_length,iovec_t header,iovec_t tag,char ** error_details)188 grpc_status_code alts_iovec_record_protocol_integrity_only_protect(
189     alts_iovec_record_protocol* rp, const iovec_t* unprotected_vec,
190     size_t unprotected_vec_length, iovec_t header, iovec_t tag,
191     char** error_details) {
192   /* Input sanity checks.  */
193   if (rp == nullptr) {
194     maybe_copy_error_msg("Input iovec_record_protocol is nullptr.",
195                          error_details);
196     return GRPC_STATUS_INVALID_ARGUMENT;
197   }
198   if (!rp->is_integrity_only) {
199     maybe_copy_error_msg(
200         "Integrity-only operations are not allowed for this object.",
201         error_details);
202     return GRPC_STATUS_FAILED_PRECONDITION;
203   }
204   if (!rp->is_protect) {
205     maybe_copy_error_msg("Protect operations are not allowed for this object.",
206                          error_details);
207     return GRPC_STATUS_FAILED_PRECONDITION;
208   }
209   grpc_status_code status =
210       ensure_header_and_tag_length(rp, header, tag, error_details);
211   if (status != GRPC_STATUS_OK) {
212     return status;
213   }
214   /* Unprotected data should not be zero length.  */
215   size_t data_length =
216       get_total_length(unprotected_vec, unprotected_vec_length);
217   /* Sets frame header.  */
218   status = write_frame_header(data_length + rp->tag_length,
219                               static_cast<unsigned char*>(header.iov_base),
220                               error_details);
221   if (status != GRPC_STATUS_OK) {
222     return status;
223   }
224   /* Computes frame tag by calling AEAD crypter.  */
225   size_t bytes_written = 0;
226   status = gsec_aead_crypter_encrypt_iovec(
227       rp->crypter, alts_counter_get_counter(rp->ctr),
228       alts_counter_get_size(rp->ctr), unprotected_vec, unprotected_vec_length,
229       /* plaintext_vec = */ nullptr, /* plaintext_vec_length = */ 0, tag,
230       &bytes_written, error_details);
231   if (status != GRPC_STATUS_OK) {
232     return status;
233   }
234   if (bytes_written != rp->tag_length) {
235     maybe_copy_error_msg("Bytes written expects to be the same as tag length.",
236                          error_details);
237     return GRPC_STATUS_INTERNAL;
238   }
239   /* Increments the crypter counter.  */
240   return increment_counter(rp->ctr, error_details);
241 }
242 
alts_iovec_record_protocol_integrity_only_unprotect(alts_iovec_record_protocol * rp,const iovec_t * protected_vec,size_t protected_vec_length,iovec_t header,iovec_t tag,char ** error_details)243 grpc_status_code alts_iovec_record_protocol_integrity_only_unprotect(
244     alts_iovec_record_protocol* rp, const iovec_t* protected_vec,
245     size_t protected_vec_length, iovec_t header, iovec_t tag,
246     char** error_details) {
247   /* Input sanity checks.  */
248   if (rp == nullptr) {
249     maybe_copy_error_msg("Input iovec_record_protocol is nullptr.",
250                          error_details);
251     return GRPC_STATUS_INVALID_ARGUMENT;
252   }
253   if (!rp->is_integrity_only) {
254     maybe_copy_error_msg(
255         "Integrity-only operations are not allowed for this object.",
256         error_details);
257     return GRPC_STATUS_FAILED_PRECONDITION;
258   }
259   if (rp->is_protect) {
260     maybe_copy_error_msg(
261         "Unprotect operations are not allowed for this object.", error_details);
262     return GRPC_STATUS_FAILED_PRECONDITION;
263   }
264   grpc_status_code status =
265       ensure_header_and_tag_length(rp, header, tag, error_details);
266   if (status != GRPC_STATUS_OK) return status;
267   /* Protected data should not be zero length.  */
268   size_t data_length = get_total_length(protected_vec, protected_vec_length);
269   /* Verifies frame header.  */
270   status = verify_frame_header(data_length + rp->tag_length,
271                                static_cast<unsigned char*>(header.iov_base),
272                                error_details);
273   if (status != GRPC_STATUS_OK) {
274     return status;
275   }
276   /* Verifies frame tag by calling AEAD crypter.  */
277   iovec_t plaintext = {nullptr, 0};
278   size_t bytes_written = 0;
279   status = gsec_aead_crypter_decrypt_iovec(
280       rp->crypter, alts_counter_get_counter(rp->ctr),
281       alts_counter_get_size(rp->ctr), protected_vec, protected_vec_length, &tag,
282       1, plaintext, &bytes_written, error_details);
283   if (status != GRPC_STATUS_OK || bytes_written != 0) {
284     maybe_append_error_msg(" Frame tag verification failed.", error_details);
285     return GRPC_STATUS_INTERNAL;
286   }
287   /* Increments the crypter counter.  */
288   return increment_counter(rp->ctr, error_details);
289 }
290 
alts_iovec_record_protocol_privacy_integrity_protect(alts_iovec_record_protocol * rp,const iovec_t * unprotected_vec,size_t unprotected_vec_length,iovec_t protected_frame,char ** error_details)291 grpc_status_code alts_iovec_record_protocol_privacy_integrity_protect(
292     alts_iovec_record_protocol* rp, const iovec_t* unprotected_vec,
293     size_t unprotected_vec_length, iovec_t protected_frame,
294     char** error_details) {
295   /* Input sanity checks.  */
296   if (rp == nullptr) {
297     maybe_copy_error_msg("Input iovec_record_protocol is nullptr.",
298                          error_details);
299     return GRPC_STATUS_INVALID_ARGUMENT;
300   }
301   if (rp->is_integrity_only) {
302     maybe_copy_error_msg(
303         "Privacy-integrity operations are not allowed for this object.",
304         error_details);
305     return GRPC_STATUS_FAILED_PRECONDITION;
306   }
307   if (!rp->is_protect) {
308     maybe_copy_error_msg("Protect operations are not allowed for this object.",
309                          error_details);
310     return GRPC_STATUS_FAILED_PRECONDITION;
311   }
312   /* Unprotected data should not be zero length.  */
313   size_t data_length =
314       get_total_length(unprotected_vec, unprotected_vec_length);
315   /* Ensures protected frame iovec has sufficient size.  */
316   if (protected_frame.iov_base == nullptr) {
317     maybe_copy_error_msg("Protected frame is nullptr.", error_details);
318     return GRPC_STATUS_INVALID_ARGUMENT;
319   }
320   if (protected_frame.iov_len !=
321       alts_iovec_record_protocol_get_header_length() + data_length +
322           rp->tag_length) {
323     maybe_copy_error_msg("Protected frame size is incorrect.", error_details);
324     return GRPC_STATUS_INVALID_ARGUMENT;
325   }
326   /* Writer frame header.  */
327   grpc_status_code status = write_frame_header(
328       data_length + rp->tag_length,
329       static_cast<unsigned char*>(protected_frame.iov_base), error_details);
330   if (status != GRPC_STATUS_OK) {
331     return status;
332   }
333   /* Encrypt unprotected data by calling AEAD crypter.  */
334   unsigned char* ciphertext_buffer =
335       static_cast<unsigned char*>(protected_frame.iov_base) +
336       alts_iovec_record_protocol_get_header_length();
337   iovec_t ciphertext = {ciphertext_buffer, data_length + rp->tag_length};
338   size_t bytes_written = 0;
339   status = gsec_aead_crypter_encrypt_iovec(
340       rp->crypter, alts_counter_get_counter(rp->ctr),
341       alts_counter_get_size(rp->ctr), /* aad_vec = */ nullptr,
342       /* aad_vec_length = */ 0, unprotected_vec, unprotected_vec_length,
343       ciphertext, &bytes_written, error_details);
344   if (status != GRPC_STATUS_OK) {
345     return status;
346   }
347   if (bytes_written != data_length + rp->tag_length) {
348     maybe_copy_error_msg(
349         "Bytes written expects to be data length plus tag length.",
350         error_details);
351     return GRPC_STATUS_INTERNAL;
352   }
353   /* Increments the crypter counter. */
354   return increment_counter(rp->ctr, error_details);
355 }
356 
alts_iovec_record_protocol_privacy_integrity_unprotect(alts_iovec_record_protocol * rp,iovec_t header,const iovec_t * protected_vec,size_t protected_vec_length,iovec_t unprotected_data,char ** error_details)357 grpc_status_code alts_iovec_record_protocol_privacy_integrity_unprotect(
358     alts_iovec_record_protocol* rp, iovec_t header,
359     const iovec_t* protected_vec, size_t protected_vec_length,
360     iovec_t unprotected_data, char** error_details) {
361   /* Input sanity checks.  */
362   if (rp == nullptr) {
363     maybe_copy_error_msg("Input iovec_record_protocol is nullptr.",
364                          error_details);
365     return GRPC_STATUS_INVALID_ARGUMENT;
366   }
367   if (rp->is_integrity_only) {
368     maybe_copy_error_msg(
369         "Privacy-integrity operations are not allowed for this object.",
370         error_details);
371     return GRPC_STATUS_FAILED_PRECONDITION;
372   }
373   if (rp->is_protect) {
374     maybe_copy_error_msg(
375         "Unprotect operations are not allowed for this object.", error_details);
376     return GRPC_STATUS_FAILED_PRECONDITION;
377   }
378   /* Protected data size should be no less than tag size.  */
379   size_t protected_data_length =
380       get_total_length(protected_vec, protected_vec_length);
381   if (protected_data_length < rp->tag_length) {
382     maybe_copy_error_msg(
383         "Protected data length should be more than the tag length.",
384         error_details);
385     return GRPC_STATUS_INVALID_ARGUMENT;
386   }
387   /* Ensures header has sufficient size.  */
388   if (header.iov_base == nullptr) {
389     maybe_copy_error_msg("Header is nullptr.", error_details);
390     return GRPC_STATUS_INVALID_ARGUMENT;
391   }
392   if (header.iov_len != alts_iovec_record_protocol_get_header_length()) {
393     maybe_copy_error_msg("Header length is incorrect.", error_details);
394     return GRPC_STATUS_INVALID_ARGUMENT;
395   }
396   /* Ensures unprotected data iovec has sufficient size.  */
397   if (unprotected_data.iov_len != protected_data_length - rp->tag_length) {
398     maybe_copy_error_msg("Unprotected data size is incorrect.", error_details);
399     return GRPC_STATUS_INVALID_ARGUMENT;
400   }
401   /* Verify frame header.  */
402   grpc_status_code status = verify_frame_header(
403       protected_data_length, static_cast<unsigned char*>(header.iov_base),
404       error_details);
405   if (status != GRPC_STATUS_OK) {
406     return status;
407   }
408   /* Decrypt protected data by calling AEAD crypter.  */
409   size_t bytes_written = 0;
410   status = gsec_aead_crypter_decrypt_iovec(
411       rp->crypter, alts_counter_get_counter(rp->ctr),
412       alts_counter_get_size(rp->ctr), /* aad_vec = */ nullptr,
413       /* aad_vec_length = */ 0, protected_vec, protected_vec_length,
414       unprotected_data, &bytes_written, error_details);
415   if (status != GRPC_STATUS_OK) {
416     maybe_append_error_msg(" Frame decryption failed.", error_details);
417     return GRPC_STATUS_INTERNAL;
418   }
419   if (bytes_written != protected_data_length - rp->tag_length) {
420     maybe_copy_error_msg(
421         "Bytes written expects to be protected data length minus tag length.",
422         error_details);
423     return GRPC_STATUS_INTERNAL;
424   }
425   /* Increments the crypter counter. */
426   return increment_counter(rp->ctr, error_details);
427 }
428 
alts_iovec_record_protocol_create(gsec_aead_crypter * crypter,size_t overflow_size,bool is_client,bool is_integrity_only,bool is_protect,alts_iovec_record_protocol ** rp,char ** error_details)429 grpc_status_code alts_iovec_record_protocol_create(
430     gsec_aead_crypter* crypter, size_t overflow_size, bool is_client,
431     bool is_integrity_only, bool is_protect, alts_iovec_record_protocol** rp,
432     char** error_details) {
433   if (crypter == nullptr || rp == nullptr) {
434     maybe_copy_error_msg(
435         "Invalid nullptr arguments to alts_iovec_record_protocol create.",
436         error_details);
437     return GRPC_STATUS_INVALID_ARGUMENT;
438   }
439   alts_iovec_record_protocol* impl = static_cast<alts_iovec_record_protocol*>(
440       gpr_zalloc(sizeof(alts_iovec_record_protocol)));
441   /* Gets counter length.  */
442   size_t counter_length = 0;
443   grpc_status_code status =
444       gsec_aead_crypter_nonce_length(crypter, &counter_length, error_details);
445   if (status != GRPC_STATUS_OK) {
446     goto cleanup;
447   }
448   /* Creates counters.  */
449   status =
450       alts_counter_create(is_protect ? !is_client : is_client, counter_length,
451                           overflow_size, &impl->ctr, error_details);
452   if (status != GRPC_STATUS_OK) {
453     goto cleanup;
454   }
455   /* Gets tag length.  */
456   status =
457       gsec_aead_crypter_tag_length(crypter, &impl->tag_length, error_details);
458   if (status != GRPC_STATUS_OK) {
459     goto cleanup;
460   }
461   impl->crypter = crypter;
462   impl->is_integrity_only = is_integrity_only;
463   impl->is_protect = is_protect;
464   *rp = impl;
465   return GRPC_STATUS_OK;
466 cleanup:
467   alts_counter_destroy(impl->ctr);
468   gpr_free(impl);
469   return GRPC_STATUS_FAILED_PRECONDITION;
470 }
471 
alts_iovec_record_protocol_destroy(alts_iovec_record_protocol * rp)472 void alts_iovec_record_protocol_destroy(alts_iovec_record_protocol* rp) {
473   if (rp != nullptr) {
474     alts_counter_destroy(rp->ctr);
475     gsec_aead_crypter_destroy(rp->crypter);
476     gpr_free(rp);
477   }
478 }
479