Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CCM-AES from Linux Kernel

I need to be compatible with Solaris crypto mech SUN_CKM_AES_CCM. In Linux, I believe I should setup an AEAD request to get "ccm(aes)" mech. Documentation for Linux Crypto does seem rather poor, the best example appears to be tcrypt.c test, and kernel sources.

From Solaris, I did a test encryption of a 512 byte block, with 16 byte hmac, and 12 byte iv. This needs to stay the same, and hopefully the results be identical.

However, what I think should would work, does not;

   struct crypto_aead *tfm = NULL;
   struct aead_request *req;
   unsigned char key[16] = {
    0x5c, 0x95, 0x64, 0x42, 0x00, 0x82, 0x1c, 0x9e,
    0xd4, 0xac, 0x01, 0x83, 0xc4, 0x9c, 0x14, 0x97
   };
  unsigned int ivsize;
  int ret;
  struct scatterlist plaintext[1];
  struct scatterlist ciphertext[1];
  struct scatterlist hmactext[1];
  unsigned char *plaindata = NULL;
  unsigned char *cipherdata = NULL;
  unsigned char *hmacdata = NULL;
  unsigned char *ivp = NULL;
  int i;
  unsigned char d;
  struct tcrypt_result result;

  tfm = crypto_alloc_aead("ccm(aes)", 0, 0);
  init_completion(&result.completion);
  req = aead_request_alloc(tfm, GFP_KERNEL);
  aead_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG,
                          cipher_work_done, &result);

  crypto_aead_clear_flags(tfm, ~0);

  ret = crypto_aead_setkey(tfm, key, sizeof(key));

  ret = crypto_aead_setauthsize(tfm, 16); // authsize is hmac?

  ivsize = crypto_aead_ivsize(tfm);
  if (ivsize != 12) {
    printk("ivsize is not 12 %d - this needs to be fixed\n", ivsize);
  }

  plaindata  = kmalloc(512, GFP_KERNEL);
  cipherdata = kmalloc(512, GFP_KERNEL);
  hmacdata   = kmalloc(16, GFP_KERNEL);
  ivp        = kmalloc(ivsize, GFP_KERNEL);

  if (!plaindata || !cipherdata || !hmacdata || !ivp) goto out;

  // put 00 01 02 03 ... in the input buffer...
  for (i = 0, d = 0; i < 512; i++, d++)
    plaindata[i] = d;

  memset(cipherdata, 0, 512);
  memset(hmacdata, 0, 16);
  memset(ivp, 0, ivsize);

  // Put a8 a9 aa .... in iv
  for (i = 0,d=0xa8; i < 12; i++, d++)
    ivp[i] = d;

  sg_init_one(&plaintext[0],  plaindata,  512);
  sg_init_one(&ciphertext[0], cipherdata, 512);
  sg_init_one(&hmactext[0],   hmacdata,   16);

  aead_request_set_crypt(req, plaintext, ciphertext, 512, ivp);

  aead_request_set_assoc(req, hmactext, 16);

  ret = crypto_aead_encrypt(req);

  printk("cipher call returns %d \n", ret);

And what we get back is that ivsize is 16 (and I see no way to set it to 12), and that encrypt fails with "-22" or EINVAL. There are lots of errors checking in the code, removed here, that confirm all prior call return success.

As far as I can tell, I follow the tcrypt.c sources pretty close. However, I wonder if the forced ivsize = 16 will mean I can not use the supplied algorithm anyway. That aside, it would be nice to see the encrypt call succeed and what is put in the cipherdata output.

The code is put into a kernel module, and run at _init() time. Initially I used blkcipher "aes", which works, but is not the ccm-aes variant. This made me change to use aead, which I can not get to work.

like image 211
lundman Avatar asked Nov 08 '12 08:11

lundman


1 Answers

Ok, this is what I have learnt.

1) Let's call the Application's iv nonce. And let's call the internal crypto's iv iv. It turns out that the Solaris code is using nonce-len=12, but the CCM-AES algorithm still uses iv-len=16.

From Solaris kernel sources, iv is made up with:

iv[0] = 1..7, based on ivlen 16 - noncelen 12 = 2.
iv[1] = the nonce data (12 bytes).
iv[14] = 0
iv[15] = 1

So, on Linux I want "ccm(aes)" with ivlen 16, and prepare the iv from nonce properly.

2) When calling crypto_aead_encrypt() the prior call of aead_request_set_assoc() is ignored, and HMAC is put at the end of the cipher buffer. In my case, at ciphertext[512], for 16 bytes. So the input needed to be +16 in length.

Using scatterlist, the HMAC "at the end" can be somewhere different if set up correctly.

3) When calling crypto_aead_decrypt() the cryptolen should be +16 (cipherinputlen + maclen). The MAC is read from end of input buffer, ie, ciphertext[512] for 16 bytes. Which can also be a separate buffer using scatterlist.

4) crypto_aead_setauthsize() checks that the len given is correct, then does nothing with it. Don't think this actually sets the size!

5) aead_request_set_assoc() has to be set, even if it is just to a buffer of zeros.

like image 113
lundman Avatar answered Oct 12 '22 08:10

lundman