text: /*
   *  mbed TLS CCM implementation
   *  Slightly modified to match 3DS behavior described here https://www.3dbrew.org/wiki/AES_Registers#CCM_mode_pitfall
   *
   *  Copyright (C) 2006-2015, ARM Limited, All Rights Reserved
   *  SPDX-License-Identifier: Apache-2.0
   *
   *  Licensed under the Apache License, Version 2.0 (the "License"); you may
   *  not use this file except in compliance with the License.
   *  You may obtain a copy of the License at
   *
   *  http://www.apache.org/licenses/LICENSE-2.0
   *
   *  Unless required by applicable law or agreed to in writing, software
   *  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
   *  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   *  See the License for the specific language governing permissions and
   *  limitations under the License.
   */
  
  #include <mbedtls/ccm.h>
  
  #define CCM_VALIDATE_RET( cond ) \
      MBEDTLS_INTERNAL_VALIDATE_RET( cond, MBEDTLS_ERR_CCM_BAD_INPUT )
  #define CCM_VALIDATE( cond ) \
      MBEDTLS_INTERNAL_VALIDATE( cond )
  
  #define CCM_ENCRYPT 0
  #define CCM_DECRYPT 1
  
  /*
   * Macros for common operations.
   * Results in smaller compiled code than static inline functions.
   */
  
  /*
   * Update the CBC-MAC state in y using a block in b
   * (Always using b as the source helps the compiler optimise a bit better.)
   */
  #define UPDATE_CBC_MAC                                                      \
      for( i = 0; i < 16; i++ )                                               \
          y[i] ^= b[i];                                                       \
                                                                              \
      if( ( ret = mbedtls_cipher_update( &ctx->cipher_ctx, y, 16, y, &olen ) ) != 0 ) \
          return( ret );
  
  /*
   * Encrypt or decrypt a partial block with CTR
   * Warning: using b for temporary storage! src and dst must not be b!
   * This avoids allocating one more 16 bytes buffer while allowing src == dst.
   */
  #define CTR_CRYPT( dst, src, len  )                                            \
      do                                                                  \
      {                                                                   \
          if( ( ret = mbedtls_cipher_update( &ctx->cipher_ctx, ctr,       \
                                             16, b, &olen ) ) != 0 )      \
          {                                                               \
              return( ret );                                              \
          }                                                               \
                                                                          \
          for( i = 0; i < (len); i++ )                                    \
              (dst)[i] = (src)[i] ^ b[i];                                 \
      } while( 0 )
  
  /*
   * Authenticated encryption or decryption
   */
  static int ccm_auth_crypt_3ds( mbedtls_ccm_context *ctx, int mode, size_t length,
                             const unsigned char *iv, size_t iv_len,
                             const unsigned char *add, size_t add_len,
                             const unsigned char *input, unsigned char *output,
                             unsigned char *tag, size_t tag_len )
  {
      int ret;
      unsigned char i;
      unsigned char q;
      size_t len_left, olen;
      unsigned char b[16];
      unsigned char y[16];
      unsigned char ctr[16];
      const unsigned char *src;
      unsigned char *dst;
  
      /*
       * Check length requirements: SP800-38C A.1
       * Additional requirement: a < 2^16 - 2^8 to simplify the code.
       * 'length' checked later (when writing it to the first block)
       *
       * Also, loosen the requirements to enable support for CCM* (IEEE 802.15.4).
       */
      if( tag_len == 2 || tag_len > 16 || tag_len % 2 != 0 )
          return( MBEDTLS_ERR_CCM_BAD_INPUT );
  
      /* Also implies q is within bounds */
      if( iv_len < 7 || iv_len > 13 )
          return( MBEDTLS_ERR_CCM_BAD_INPUT );
  
      if( add_len > 0xFF00 )
          return( MBEDTLS_ERR_CCM_BAD_INPUT );
  
      q = 16 - 1 - (unsigned char) iv_len;
  
      /*
       * First block B_0:
       * 0        .. 0        flags
       * 1        .. iv_len   nonce (aka iv)
       * iv_len+1 .. 15       length
       *
       * With flags as (bits):
       * 7        0
       * 6        add present?
       * 5 .. 3   (t - 2) / 2
       * 2 .. 0   q - 1
       */
      b[0] = 0;
      b[0] |= ( add_len > 0 ) << 6;
      b[0] |= ( ( tag_len - 2 ) / 2 ) << 3;
      b[0] |= q - 1;
  
      memcpy( b + 1, iv, iv_len );
  
      /* Only modified here besides function name changes */
      len_left = length + ( length%16 == 0 ? 0 : 16-(length%16) );
      for( i = 0; i < q; i++, len_left >>= 8 )
          b[15-i] = (unsigned char)( len_left & 0xFF );
  
      if( len_left > 0 )
          return( MBEDTLS_ERR_CCM_BAD_INPUT );
  
  
      /* Start CBC-MAC with first block */
      memset( y, 0, 16 );
      UPDATE_CBC_MAC;
  
      /*
       * If there is additional data, update CBC-MAC with
       * add_len, add, 0 (padding to a block boundary)
       */
      if( add_len > 0 )
      {
          size_t use_len;
          len_left = add_len;
          src = add;
  
          memset( b, 0, 16 );
          b[0] = (unsigned char)( ( add_len >> 8 ) & 0xFF );
          b[1] = (unsigned char)( ( add_len      ) & 0xFF );
  
          use_len = len_left < 16 - 2 ? len_left : 16 - 2;
          memcpy( b + 2, src, use_len );
          len_left -= use_len;
          src += use_len;
  
          UPDATE_CBC_MAC;
  
          while( len_left > 0 )
          {
              use_len = len_left > 16 ? 16 : len_left;
  
              memset( b, 0, 16 );
              memcpy( b, src, use_len );
              UPDATE_CBC_MAC;
  
              len_left -= use_len;
              src += use_len;
          }
      }
  
      /*
       * Prepare counter block for encryption:
       * 0        .. 0        flags
       * 1        .. iv_len   nonce (aka iv)
       * iv_len+1 .. 15       counter (initially 1)
       *
       * With flags as (bits):
       * 7 .. 3   0
       * 2 .. 0   q - 1
       */
      ctr[0] = q - 1;
      memcpy( ctr + 1, iv, iv_len );
      memset( ctr + 1 + iv_len, 0, q );
      ctr[15] = 1;
  
      /*
       * Authenticate and {en,de}crypt the message.
       *
       * The only difference between encryption and decryption is
       * the respective order of authentication and {en,de}cryption.
       */
      len_left = length;
      src = input;
      dst = output;
  
      while( len_left > 0 )
      {
          size_t use_len = len_left > 16 ? 16 : len_left;
  
          if( mode == CCM_ENCRYPT )
          {
              memset( b, 0, 16 );
              memcpy( b, src, use_len );
              UPDATE_CBC_MAC;
          }
  
          CTR_CRYPT( dst, src, use_len );
  
          if( mode == CCM_DECRYPT )
          {
              memset( b, 0, 16 );
              memcpy( b, dst, use_len );
              UPDATE_CBC_MAC;
          }
  
          dst += use_len;
          src += use_len;
          len_left -= use_len;
  
          /*
           * Increment counter.
           * No need to check for overflow thanks to the length check above.
           */
          for( i = 0; i < q; i++ )
              if( ++ctr[15-i] != 0 )
                  break;
      }
  
      /*
       * Authentication: reset counter and crypt/mask internal tag
       */
      for( i = 0; i < q; i++ )
          ctr[15-i] = 0;
  
      CTR_CRYPT( y, y, 16 );
      memcpy( tag, y, tag_len );
  
      return( 0 );
  }
  
  /*
   * Authenticated encryption
   */
  int mbedtls_ccm_star_encrypt_and_tag_3ds( mbedtls_ccm_context *ctx, size_t length,
                           const unsigned char *iv, size_t iv_len,
                           const unsigned char *add, size_t add_len,
                           const unsigned char *input, unsigned char *output,
                           unsigned char *tag, size_t tag_len )
  {
      CCM_VALIDATE_RET( ctx != NULL );
      CCM_VALIDATE_RET( iv != NULL );
      CCM_VALIDATE_RET( add_len == 0 || add != NULL );
      CCM_VALIDATE_RET( length == 0 || input != NULL );
      CCM_VALIDATE_RET( length == 0 || output != NULL );
      CCM_VALIDATE_RET( tag_len == 0 || tag != NULL );
      return( ccm_auth_crypt_3ds( ctx, CCM_ENCRYPT, length, iv, iv_len,
                              add, add_len, input, output, tag, tag_len ) );
  }
  
  int mbedtls_ccm_encrypt_and_tag_3ds( mbedtls_ccm_context *ctx, size_t length,
                           const unsigned char *iv, size_t iv_len,
                           const unsigned char *add, size_t add_len,
                           const unsigned char *input, unsigned char *output,
                           unsigned char *tag, size_t tag_len )
  {
      CCM_VALIDATE_RET( ctx != NULL );
      CCM_VALIDATE_RET( iv != NULL );
      CCM_VALIDATE_RET( add_len == 0 || add != NULL );
      CCM_VALIDATE_RET( length == 0 || input != NULL );
      CCM_VALIDATE_RET( length == 0 || output != NULL );
      CCM_VALIDATE_RET( tag_len == 0 || tag != NULL );
      if( tag_len == 0 )
          return( MBEDTLS_ERR_CCM_BAD_INPUT );
  
      return( mbedtls_ccm_star_encrypt_and_tag_3ds( ctx, length, iv, iv_len, add,
                  add_len, input, output, tag, tag_len ) );
  }
  
  /*
   * Authenticated decryption
   */
  int mbedtls_ccm_star_auth_decrypt_3ds( mbedtls_ccm_context *ctx, size_t length,
                        const unsigned char *iv, size_t iv_len,
                        const unsigned char *add, size_t add_len,
                        const unsigned char *input, unsigned char *output,
                        const unsigned char *tag, size_t tag_len )
  {
      int ret;
      unsigned char check_tag[16];
      unsigned char i;
      int diff;
  
      CCM_VALIDATE_RET( ctx != NULL );
      CCM_VALIDATE_RET( iv != NULL );
      CCM_VALIDATE_RET( add_len == 0 || add != NULL );
      CCM_VALIDATE_RET( length == 0 || input != NULL );
      CCM_VALIDATE_RET( length == 0 || output != NULL );
      CCM_VALIDATE_RET( tag_len == 0 || tag != NULL );
  
      if( ( ret = ccm_auth_crypt_3ds( ctx, CCM_DECRYPT, length,
                                  iv, iv_len, add, add_len,
                                  input, output, check_tag, tag_len ) ) != 0 )
      {
          return( ret );
      }
  
      /* Check tag in "constant-time" */
      for( diff = 0, i = 0; i < tag_len; i++ )
          diff |= tag[i] ^ check_tag[i];
  
      if( diff != 0 )
      {
          mbedtls_platform_zeroize( output, length );
          return( MBEDTLS_ERR_CCM_AUTH_FAILED );
      }
  
      return( 0 );
  }
  
  int mbedtls_ccm_auth_decrypt_3ds( mbedtls_ccm_context *ctx, size_t length,
                        const unsigned char *iv, size_t iv_len,
                        const unsigned char *add, size_t add_len,
                        const unsigned char *input, unsigned char *output,
                        const unsigned char *tag, size_t tag_len )
  {
      CCM_VALIDATE_RET( ctx != NULL );
      CCM_VALIDATE_RET( iv != NULL );
      CCM_VALIDATE_RET( add_len == 0 || add != NULL );
      CCM_VALIDATE_RET( length == 0 || input != NULL );
      CCM_VALIDATE_RET( length == 0 || output != NULL );
      CCM_VALIDATE_RET( tag_len == 0 || tag != NULL );
  
      if( tag_len == 0 )
          return( MBEDTLS_ERR_CCM_BAD_INPUT );
  
      return( mbedtls_ccm_star_auth_decrypt_3ds( ctx, length, iv, iv_len, add,
                  add_len, input, output, tag, tag_len ) );
  }