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