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