text: /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
  /* lib/crypto/nss/enc_provider/enc_gen.c */
  /*
   * Copyright (c) 2010 Red Hat, Inc.
   * All Rights Reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   *  * Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   *
   *  * Redistributions in binary form must reproduce the above
   *    copyright notice, this list of conditions and the following
   *    disclaimer in the documentation and/or other materials provided
   *    with the distribution.
   *
   *  * Neither the name of Red Hat, Inc., nor the names of its
   *    contributors may be used to endorse or promote products derived
   *    from this software without specific prior written permission.
   *
   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
   * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
   * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
   * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
   * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
   * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
   * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
   * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
   * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
   * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   */
  
  #include "crypto_int.h"
  #include "nss_gen.h"
  #include "seccomon.h"
  #include "secmod.h"
  #include "pk11pub.h"
  #include "nss.h"
  
  /* 512 bits is bigger than anything defined to date */
  #define MAX_KEY_LENGTH 64
  #define MAX_BLOCK_SIZE 64
  
  static NSSInitContext *k5_nss_ctx = NULL;
  static pid_t k5_nss_pid = 0;
  static k5_mutex_t k5_nss_lock = K5_MUTEX_PARTIAL_INITIALIZER;
  
  struct stream_state {
      struct stream_state *loopback;  /* To detect copying */
      pid_t pid;                      /* To detect use across fork */
      PK11Context *ctx;
  };
  
  struct cached_key {
      pid_t pid;                  /* To detect use across fork */
      PK11SymKey *symkey;
  };
  
  krb5_error_code
  k5_nss_map_error(int nss_error)
  {
      /* Currently KRB5 does not define a full set of CRYPTO failures.
       * for now just use KRB5_CRYPTO_INTERNAL.  We really should return
       * errors for Not logged in, and maybe a few others. */
      return KRB5_CRYPTO_INTERNAL;
  }
  
  krb5_error_code
  k5_nss_map_last_error(void)
  {
      return k5_nss_map_error(PORT_GetError());
  }
  
  int
  krb5int_crypto_impl_init(void)
  {
      return k5_mutex_finish_init(&k5_nss_lock);
  }
  
  void
  krb5int_crypto_impl_cleanup(void)
  {
      k5_mutex_destroy(&k5_nss_lock);
  }
  
  /*
   * krb5 doesn't have a call into the crypto engine to initialize it, so we do
   * it here.  This code will try to piggyback on any application initialization
   * done to NSS.  Otherwise get our one library init context.
   */
  #define NSS_KRB5_CONFIGDIR "sql:/etc/pki/nssdb"
  krb5_error_code
  k5_nss_init(void)
  {
      PRUint32 flags = NSS_INIT_READONLY | NSS_INIT_NOROOTINIT;
      krb5_error_code ret;
      SECStatus rv;
      pid_t pid;
  
      ret = k5_mutex_lock(&k5_nss_lock);
      if (ret)
          return ret;
  
      pid = getpid();
      if (k5_nss_ctx != NULL) {
          /* Do nothing if the existing context is still good. */
          if (k5_nss_pid == pid)
              goto cleanup;
          /* The caller has forked.  Restart the NSS modules.  This will
           * invalidate all of our PKCS11 handles, which we're prepared for. */
          rv = SECMOD_RestartModules(TRUE);
          if (rv != SECSuccess) {
              ret = k5_nss_map_last_error();
              goto cleanup;
          }
          k5_nss_pid = pid;
          goto cleanup;
      }
      k5_nss_ctx = NSS_InitContext(NSS_KRB5_CONFIGDIR, "", "", "", NULL, flags);
      if (k5_nss_ctx == NULL) {
          /* There may be no system database; try again without it. */
          flags |= NSS_INIT_NOMODDB | NSS_INIT_NOCERTDB;
          k5_nss_ctx = NSS_InitContext(NULL, "", "", "", NULL, flags);
          if (k5_nss_ctx == NULL) {
              ret = k5_nss_map_last_error();
              goto cleanup;
          }
      }
      k5_nss_pid = pid;
  
  cleanup:
      k5_mutex_unlock(&k5_nss_lock);
      return ret;
  }
  
  PK11Context *
  k5_nss_create_context(krb5_key krb_key, CK_MECHANISM_TYPE mechanism,
                        CK_ATTRIBUTE_TYPE operation, SECItem *param)
  {
      struct cached_key *ckey = krb_key->cache;
  
      return PK11_CreateContextBySymKey(mechanism, operation, ckey->symkey,
                                        param);
  }
  
  static void inline
  xor(unsigned char *x, unsigned char *y, int size)
  {
      int i;
  
  #define ALIGNED(x,type) (!(((size_t)(x))&(sizeof(type)-1)))
      if (ALIGNED(x,unsigned long) && ALIGNED(y, unsigned long)
          && ALIGNED(size, unsigned long)) {
          unsigned long *ux = (unsigned long *)x;
          unsigned long *uy = (unsigned long *)y;
          for (i=0; i < (int)(size/sizeof(unsigned long)); i++) {
              *ux++ ^= *uy++;
          }
          return;
      }
      for (i=0; i < size; i++) {
          *x++ ^= *y++;
      }
  }
  
  krb5_error_code
  k5_nss_gen_block_iov(krb5_key krb_key, CK_MECHANISM_TYPE mech,
                       CK_ATTRIBUTE_TYPE operation, const krb5_data *ivec,
                       krb5_crypto_iov *data, size_t num_data)
  {
      krb5_error_code ret = 0;
      PK11Context *ctx = NULL;
      SECStatus rv;
      SECItem *param = NULL;
      struct iov_block_state input_pos, output_pos;
      unsigned char storage[MAX_BLOCK_SIZE];
      unsigned char iv0[MAX_BLOCK_SIZE];
      unsigned char *ptr = NULL,*lastptr = NULL;
      SECItem iv;
      size_t blocksize;
      int length = 0;
      int lastblock = -1;
      int currentblock;
  
      IOV_BLOCK_STATE_INIT(&input_pos);
      IOV_BLOCK_STATE_INIT(&output_pos);
  
      blocksize = PK11_GetBlockSize(mech, NULL);
      assert(blocksize <= sizeof(storage));
  
      if (ivec && ivec->data) {
          iv.data = (unsigned char *)ivec->data;
          iv.len = ivec->length;
          if (operation == CKA_DECRYPT) {
              int i, inputlength;
  
              /* Count the blocks so we know which block is last. */
              for (i = 0, inputlength = 0; i < (int)num_data; i++) {
                  krb5_crypto_iov *iov = &data[i];
  
                  if (ENCRYPT_IOV(iov))
                      inputlength += iov->data.length;
              }
              lastblock = (inputlength/blocksize) -1;
          }
      } else {
          memset(iv0, 0, sizeof(iv0));
          iv.data = iv0;
          iv.len = blocksize;
      }
      param = PK11_ParamFromIV(mech, &iv);
  
      ctx = k5_nss_create_context(krb_key, mech, operation, param);
      if (ctx == NULL) {
          ret = k5_nss_map_last_error();
          goto done;
      }
  
      for (currentblock = 0;;currentblock++) {
          if (!krb5int_c_iov_get_block_nocopy(storage, blocksize, data, num_data,
                                              &input_pos, &ptr))
              break;
  
          lastptr = NULL;
  
          /* only set if we are decrypting */
          if (lastblock == currentblock)
              memcpy(ivec->data, ptr, blocksize);
  
          rv = PK11_CipherOp(ctx, ptr, &length, blocksize, ptr, blocksize);
          if (rv != SECSuccess) {
              ret = k5_nss_map_last_error();
              break;
          }
  
          lastptr = ptr;
          krb5int_c_iov_put_block_nocopy(data, num_data, storage, blocksize,
                                         &output_pos, ptr);
      }
  
      if (lastptr && ivec && ivec->data && operation == CKA_ENCRYPT) {
          memcpy(ivec->data, lastptr, blocksize);
      }
  done:
      if (ctx) {
          PK11_Finalize(ctx);
          PK11_DestroyContext(ctx, PR_TRUE);
      }
      if (param)
          SECITEM_FreeItem(param, PR_TRUE);
      return ret;
  }
  
  krb5_error_code
  k5_nss_stream_init_state(krb5_data *new_state)
  {
      struct stream_state *sstate;
  
      /* Create a state structure with an uninitialized context. */
      sstate = calloc(1, sizeof(*sstate));
      if (sstate == NULL)
          return ENOMEM;
      sstate->loopback = NULL;
      new_state->data = (char *) sstate;
      new_state->length = sizeof(*sstate);
      return 0;
  }
  
  void
  k5_nss_stream_free_state(krb5_data *state)
  {
      struct stream_state *sstate = (struct stream_state *) state->data;
  
      /* Clean up the OpenSSL context if it was initialized. */
      if (sstate && sstate->loopback == sstate) {
          PK11_Finalize(sstate->ctx);
          PK11_DestroyContext(sstate->ctx, PR_TRUE);
      }
      free(sstate);
  }
  
  krb5_error_code
  k5_nss_gen_stream_iov(krb5_key krb_key, krb5_data *state,
                        CK_MECHANISM_TYPE mech, CK_ATTRIBUTE_TYPE operation,
                        krb5_crypto_iov *data, size_t num_data)
  {
      int ret = 0;
      PK11Context *ctx = NULL;
      SECStatus rv;
      SECItem  param;
      krb5_crypto_iov *iov;
      struct stream_state *sstate = NULL;
      int i;
  
      param.data = NULL;
      param.len = 0;
  
      sstate = (state == NULL) ? NULL : (struct stream_state *) state->data;
      if (sstate == NULL || sstate->loopback == NULL) {
          ctx = k5_nss_create_context(krb_key, mech, operation, &param);
          if (ctx == NULL) {
              ret = k5_nss_map_last_error();
              goto done;
          }
          if (sstate) {
              sstate->loopback = sstate;
              sstate->pid = getpid();
              sstate->ctx = ctx;
          }
      } else {
          /* Cipher state can't be copied or used across a fork. */
          if (sstate->loopback != sstate || sstate->pid != getpid())
              return EINVAL;
          ctx = sstate->ctx;
      }
  
      for (i=0; i < (int)num_data; i++) {
          int return_length;
          iov = &data[i];
          if (iov->data.length <= 0)
              break;
  
          if (ENCRYPT_IOV(iov)) {
              rv = PK11_CipherOp(ctx, (unsigned char *)iov->data.data,
                                 &return_length, iov->data.length,
                                 (unsigned char *)iov->data.data,
                                 iov->data.length);
              if (rv != SECSuccess) {
                  ret = k5_nss_map_last_error();
                  goto done;
              }
              iov->data.length = return_length;
          }
      }
  done:
      if (!state && ctx) {
          PK11_Finalize(ctx);
          PK11_DestroyContext(ctx, PR_TRUE);
      }
      return ret;
  }
  
  krb5_error_code
  k5_nss_gen_cts_iov(krb5_key krb_key, CK_MECHANISM_TYPE mech,
                     CK_ATTRIBUTE_TYPE operation, const krb5_data *ivec,
                     krb5_crypto_iov *data, size_t num_data)
  {
      krb5_error_code ret = 0;
      PK11Context *ctx = NULL;
      SECStatus rv;
      SECItem *param = NULL;
      struct iov_block_state input_pos, output_pos;
      unsigned char storage[MAX_BLOCK_SIZE];
      unsigned char recover1[MAX_BLOCK_SIZE];
      unsigned char recover2[MAX_BLOCK_SIZE];
      unsigned char block1[MAX_BLOCK_SIZE];
      unsigned char block2[MAX_BLOCK_SIZE];
      unsigned char iv0[MAX_BLOCK_SIZE];
      unsigned char *ptr = NULL;
      SECItem iv;
      size_t blocksize;
      size_t bulk_length, remainder;
      size_t input_length, lastblock;
      size_t length;
      int i, len;
  
      IOV_BLOCK_STATE_INIT(&input_pos);
      IOV_BLOCK_STATE_INIT(&output_pos);
  
      blocksize = PK11_GetBlockSize(mech, NULL);
      assert(blocksize <= sizeof(storage));
  
      if (ivec) {
          iv.data = (unsigned char *)ivec->data;
          iv.len = ivec->length;
      } else {
          memset(iv0, 0, sizeof(iv0));
          iv.data = iv0;
          iv.len = blocksize;
      }
      param = PK11_ParamFromIV(mech, &iv);
  
      for (i = 0, input_length = 0; i < (int)num_data; i++) {
          krb5_crypto_iov *iov = &data[i];
  
          if (ENCRYPT_IOV(iov))
              input_length += iov->data.length;
      }
      /* Must be at least a block or we fail. */
      if (input_length < blocksize) {
          ret = EINVAL;
          goto done;
      }
  
      bulk_length = (input_length / blocksize)*blocksize;
      remainder = input_length - bulk_length;
      /* Do the block swap even if the input data is aligned, only
       * drop it if we are encrypting exactly one block. */
      if (remainder == 0 && bulk_length != blocksize) {
          remainder = blocksize;
          bulk_length -= blocksize;
      }
  
      ctx = k5_nss_create_context(krb_key, mech, operation, param);
      if (ctx == NULL) {
          ret = k5_nss_map_last_error();
          goto done;
      }
  
      /* Now we bulk encrypt each block in the loop.  We need to know where to
       * stop to do special processing.  For single block operations we stop at
       * the end.  For all others we stop and the last second to last block
       * (counting partial blocks).  For decrypt operations we need to save cn-2
       * so we stop at the third to last block if it exists, otherwise cn-2 = the
       * iv. */
      lastblock = bulk_length;
      if (remainder) {
          /* We need to process the last full block and last partitial block
           * differently. */
          lastblock = bulk_length - blocksize;
          if (operation == CKA_DECRYPT) {
              if (bulk_length > blocksize) {
                  /* Stop at cn-2 so we can save it before going on. */
                  lastblock = bulk_length - 2*blocksize;
              } else {
                  /* iv is cn-2, save it now, cn - 2. */
                  memcpy(recover1, iv.data, blocksize);
                  memcpy(recover2, iv.data, blocksize);
              }
          }
      }
      for (length = 0; length < lastblock; length += blocksize) {
          if (!krb5int_c_iov_get_block_nocopy(storage, blocksize, data, num_data,
                                              &input_pos, &ptr))
              break;
  
          rv = PK11_CipherOp(ctx, ptr, &len, blocksize, ptr, blocksize);
          if (rv != SECSuccess) {
              ret = k5_nss_map_last_error();
              break;
          }
  
          krb5int_c_iov_put_block_nocopy(data, num_data, storage, blocksize,
                                         &output_pos, ptr);
      }
      if (remainder) {
          if (operation == CKA_DECRYPT) {
              if (bulk_length > blocksize) {
                  /* we need to save cn-2 */
                  if (!krb5int_c_iov_get_block_nocopy(storage, blocksize, data,
                                                      num_data, &input_pos,
                                                      &ptr))
                      goto done; /* shouldn't happen */
  
                  /* save cn-2 */
                  memcpy(recover1, ptr, blocksize);
                  memcpy(recover2, ptr, blocksize);
  
                  /* now process it as normal */
                  rv = PK11_CipherOp(ctx, ptr, &len, blocksize, ptr, blocksize);
                  if (rv != SECSuccess) {
                      ret = k5_nss_map_last_error();
                      goto done;
                  }
  
                  krb5int_c_iov_put_block_nocopy(data, num_data, storage,
                                                 blocksize, &output_pos, ptr);
              }
          }
          /* fetch the last 2 blocks */
          memset(block1, 0, blocksize); /* last block, could be partial */
          krb5int_c_iov_get_block(block2, blocksize, data, num_data, &input_pos);
          krb5int_c_iov_get_block(block1, remainder, data, num_data, &input_pos);
          if (operation == CKA_DECRYPT) {
              /* recover1 and recover2 are xor values to recover the true
               * underlying data of the last 2 decrypts. This keeps us from
               * having to try to reset our IV to do the final decryption. */
              /* Currently: block1 is cn || 0, block2 is cn-1.
               * recover1 & recover2 is set to cn-2. */
              /* recover2 recovers pn || c' from p'n-1. The raw decrypted block
               * will be p'n-1 xor with cn-2 while pn || c' = p'n-1 xor cn || 0.
               * recover2 is cn-2 xor cn || 0, so we can simple xor recover1
               * with the raw decrypted block. */
              /* recover1 recovers pn-1 from the raw decryption of cn || c'.
               * the raw decrypt of cn || c' = p'n xor cn-1 while
               * pn-1 = p'n xor cn-2
               * recover1 is cn-2 xor cn-1, so we can simple xor recover 2 with
               * the raw decrypt of cn||c' to get pn-1. */
              xor(recover1, block2, blocksize);
              xor(recover2, block1, blocksize);
              if (ivec && ivec->data)
                  memcpy(ivec->data, block2, blocksize);
          }
          rv = PK11_CipherOp(ctx, block2, &len, blocksize, block2, blocksize);
          if (rv != SECSuccess) {
              ret = k5_nss_map_last_error();
              goto done;
          }
          if (operation == CKA_DECRYPT) {
              /* block2 now has p'n-1 xor cn-2. */
              xor(block2, recover2, blocksize);
              /* block 2 now has pn || c'. */
              /* copy c' into cn || c'. */
              memcpy(block1 + remainder, block2 + remainder,
                     blocksize - remainder);
          }
          rv = PK11_CipherOp(ctx, block1, &len, blocksize, block1, blocksize);
          if (rv != SECSuccess) {
              ret = k5_nss_map_last_error();
              goto done;
          }
          if (operation == CKA_DECRYPT) {
              /* block1 now has p'n xor cn-1 */
              xor(block1, recover1, blocksize);
              /* block 1 now has pn-1 */
          } else {
              if (ivec && ivec->data) {
                  memcpy(ivec->data, block1, blocksize);
              }
          }
          krb5int_c_iov_put_block(data,num_data, block1, blocksize, &output_pos);
          krb5int_c_iov_put_block(data,num_data, block2, remainder, &output_pos);
      }
  
  done:
      if (ctx) {
          PK11_Finalize(ctx);
          PK11_DestroyContext(ctx, PR_TRUE);
      }
      if (param)
          SECITEM_FreeItem(param, PR_TRUE);
      return ret;
  }
  
  krb5_error_code
  k5_nss_gen_cbcmac_iov(krb5_key krb_key, CK_MECHANISM_TYPE mech,
                        const krb5_data *ivec, const krb5_crypto_iov *data,
                        size_t num_data, krb5_data *output)
  {
      krb5_error_code ret = 0;
      PK11Context *ctx = NULL;
      SECStatus rv;
      SECItem *param = NULL;
      struct iov_block_state input_pos, output_pos;
      unsigned char block[MAX_BLOCK_SIZE], *lastblock;
      unsigned char iv0[MAX_BLOCK_SIZE];
      SECItem iv;
      size_t blocksize;
      int length = 0;
      int currentblock;
  
      IOV_BLOCK_STATE_INIT(&input_pos);
      IOV_BLOCK_STATE_INIT(&output_pos);
  
      blocksize = PK11_GetBlockSize(mech, NULL);
      assert(blocksize <= sizeof(block));
      if (output->length < blocksize)
          return KRB5_BAD_MSIZE;
  
      if (ivec && ivec->data) {
          iv.data = (unsigned char *)ivec->data;
          iv.len = ivec->length;
      } else {
          memset(iv0, 0, sizeof(iv0));
          iv.data = iv0;
          iv.len = blocksize;
      }
      param = PK11_ParamFromIV(mech, &iv);
  
      ctx = k5_nss_create_context(krb_key, mech, CKA_ENCRYPT, param);
      if (ctx == NULL) {
          ret = k5_nss_map_last_error();
          goto done;
      }
  
      lastblock = iv.data;
      for (currentblock = 0;;currentblock++) {
          if (!krb5int_c_iov_get_block(block, blocksize, data, num_data,
                                       &input_pos))
              break;
          rv = PK11_CipherOp(ctx, block, &length, blocksize, block, blocksize);
          if (rv != SECSuccess) {
              ret = k5_nss_map_last_error();
              goto done;
          }
          lastblock = block;
      }
      memcpy(output->data, lastblock, blocksize);
  
  done:
      if (ctx) {
          PK11_Finalize(ctx);
          PK11_DestroyContext(ctx, PR_TRUE);
      }
      if (param)
          SECITEM_FreeItem(param, PR_TRUE);
      return ret;
  }
  
  void
  k5_nss_gen_cleanup(krb5_key krb_key)
  {
      struct cached_key *ckey = krb_key->cache;
  
      if (ckey) {
          PK11_FreeSymKey(ckey->symkey);
          free(ckey);
          krb_key->cache = NULL;
      }
  }
  
  krb5_error_code
  k5_nss_gen_import(krb5_key krb_key, CK_MECHANISM_TYPE mech,
                    CK_ATTRIBUTE_TYPE operation)
  {
      krb5_error_code ret = 0;
      pid_t pid = getpid();
      struct cached_key *ckey = krb_key->cache;
      PK11SymKey *symkey;
      PK11SlotInfo *slot = NULL;
      SECItem raw_key;
  #ifdef FAKE_FIPS
      PK11SymKey *wrapping_key = NULL;
      PK11Context *ctx = NULL;
      SECItem wrapped_key;
      SECItem params;
      unsigned char wrapped_key_data[MAX_KEY_LENGTH];
      unsigned char padded_key_data[MAX_KEY_LENGTH];
      int wrapping_index, series, blocksize;
      int keyLength;
      CK_MECHANISM_TYPE mechanism;
      SECStatus rv;
  #endif
  
      if (ckey && ckey->pid == pid)
          return 0;
  
      ret = k5_nss_init();
      if (ret)
          return ret;
  
      if (ckey) {
          /* Discard the no-longer-valid symkey and steal its container. */
          PK11_FreeSymKey(ckey->symkey);
          ckey->symkey = NULL;
          krb_key->cache = NULL;
      } else {
          /* Allocate a new container. */
          ckey = k5alloc(sizeof(*ckey), &ret);
          if (ckey == NULL)
              return ret;
      }
  
      slot = PK11_GetBestSlot(mech, NULL);
      if (slot == NULL) {
          ret = k5_nss_map_last_error();
          goto done;
      }
      raw_key.data = krb_key->keyblock.contents;
      raw_key.len = krb_key->keyblock.length;
  
  #ifdef FAKE_FIPS
      /* First, fetch a wrapping key. */
      wrapping_index = PK11_GetCurrentWrapIndex(slot);
      series = PK11_GetSlotSeries(slot);
      wrapping_key = PK11_GetWrapKey(slot, wrapping_index,
                                     CKM_INVALID_MECHANISM, series, NULL);
      if (wrapping_key == NULL) {
          /* One doesn't exist, create one. */
          mechanism = PK11_GetBestWrapMechanism(slot);
          keyLength = PK11_GetBestKeyLength(slot, mechanism);
          wrapping_key = PK11_TokenKeyGenWithFlags(slot, mechanism, NULL,
                                                   keyLength, NULL,
                                                   CKF_UNWRAP | CKF_ENCRYPT, 0,
                                                   NULL);
          if (!wrapping_key) {
              ret = k5_nss_map_last_error();
              goto done;
          }
          PK11_SetWrapKey(slot, wrapping_index, wrapping_key);
      }
  
      /* Now encrypt the data with the wrapping key. */
      mechanism = PK11_GetMechanism(wrapping_key);
      params.data = NULL;
      params.len = 0;
      ctx = PK11_CreateContextBySymKey(mechanism, CKA_ENCRYPT,
                                       wrapping_key, &params);
      if (ctx == NULL) {
          ret = k5_nss_map_last_error();
          goto done;
      }
  
      wrapped_key.data = wrapped_key_data;
      wrapped_key.len = sizeof(wrapped_key_data);
      blocksize = PK11_GetBlockSize(mechanism, NULL);
      keyLength = raw_key.len;
  
      /*
       * ECB modes need keys in integral multiples of the block size.
       * if the key isn't and integral multiple, pad it with zero. Unwrap
       * will use the length parameter to appropriately set the key.
       */
      if ((raw_key.len % blocksize) != 0) {
          int keyblocks = (raw_key.len + (blocksize - 1)) / blocksize;
          keyLength = keyblocks * blocksize;
          assert(keyLength <= sizeof(padded_key_data));
          memset(padded_key_data, 0, keyLength);
          memcpy(padded_key_data, raw_key.data, raw_key.len);
          raw_key.data = padded_key_data;
      }
      rv = PK11_CipherOp(ctx, wrapped_key.data, (int *)&wrapped_key.len,
                         sizeof(wrapped_key_data), raw_key.data, keyLength);
      if (keyLength != raw_key.len) {
          /* Clear our copy of the key bits. */
          memset(padded_key_data, 0, keyLength);
      }
      if (rv != SECSuccess) {
          ret = k5_nss_map_last_error();
          goto done;
      }
      PK11_Finalize(ctx);
      PK11_DestroyContext(ctx, PR_TRUE);
      ctx = NULL;
  
      /* Now now we have a 'wrapped' version of the, we can import it into
       * the token without running afoul with FIPS. */
      symkey = PK11_UnwrapSymKey(wrapping_key, mechanism, &params, &wrapped_key,
                                 mech, operation, raw_key.len);
  #else
      symkey = PK11_ImportSymKey(slot, mech, PK11_OriginGenerated, operation,
                                 &raw_key, NULL);
  #endif
      if (symkey == NULL) {
          ret = k5_nss_map_last_error();
          goto done;
      }
      ckey->pid = pid;
      ckey->symkey = symkey;
      krb_key->cache = ckey;
      ckey = NULL;
  
  done:
      free(ckey);
      if (slot)
          PK11_FreeSlot(slot);
  #ifdef FAKE_FIPS
      if (ctx) {
          PK11_Finalize(ctx);
          PK11_DestroyContext(ctx, PR_TRUE);
      }
      if (wrapping_key)
          PK11_FreeSymKey(wrapping_key);
  #endif
  
      return ret;
  }
  