openssl wtf

A minor WTF in openssl, where mixing encryption & decryption functions does not result in an error. This can lead to subtle security bugs if the code is poorly written.

(Pull request to address this issue)

A sane library should fail at the EVP_DecryptUpdate on line 123. Running this code (openssl 1.0.1) however results in:

Encrypting:
  assertion ok: EVP_EncryptInit_ex
  assertion ok: EVP_EncryptUpdate
  assertion ok: EVP_EncryptFinal_ex
  assertion ok: check ct1 len
cipher text: eb87ad64ea2696370c99e6e7

Decrypting:
  assertion ok: EVP_DecryptInit_ex
  assertion ok: EVP_DecryptUpdate
  assertion ok: EVP_DecryptFinal_ex
  assertion ok: check pt2 len
  assertion ok: pt2 == pt1
plain text: hello world

Decrypting a subset, with EVP_EncryptInit_ex
  assertion ok: EVP_EncryptInit_ex
  assertion ok: EVP_DecryptUpdate
  assertion ok: EVP_DecryptFinal_ex
plain text: hello worl

Decrypting a subset, with EVP_DecryptInit_ex
  assertion ok: EVP_DecryptInit_ex
  assertion ok: EVP_DecryptUpdate
  ASSERTION FAILURE: EVP_DecryptFinal_ex
plain text: hello worl

openssl_wtf.c:

/**
 * openssl's EVP library does not prevent you from mixing
 * encryption & decryption functions on a given ctx. This
 * can lead to subtle bugs if the code is poorly written.
 * Data will get decrypted but not authenticated!
 *
 * Compile and run with:
 * gcc -o openssl_wtf openssl_wtf.c
 *            -L/usr/lib/x86_64-linux-gnu/ -lssl -lcrypto
 * ./openssl_wtf
 */
#include <stdio.h>
#include <strings.h>
#include <openssl/bio.h>
#include <openssl/evp.h>
#include <openssl/rand.h>

void assert(int exp, char *msg) {
  if (!exp) {
    printf("  ASSERTION FAILURE: %s\n", msg);
  } else {
    printf("  assertion ok: %s\n", msg);
  }
}

int main(int argc, char **argv) {
  unsigned char *key = (unsigned char *)"blah";
 
  unsigned char iv[12] = { 0x00 };
  unsigned char tag[16] = { 0x00 };
  unsigned char pt1[] =
    {'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', 0};
  unsigned char ct1[sizeof(pt1)];
  unsigned char ct2[sizeof(pt1) - 2];
  unsigned char pt2[sizeof(pt1)];
 
  EVP_CIPHER_CTX ctx;
  int ct_offset, pt2_offset, t, r;
 
  EVP_CIPHER_CTX_init(&ctx);
 
  /* Initialize IV, don't use pseudo_bytes in real code */
  RAND_pseudo_bytes(iv, sizeof(iv));

  /* Encrypt the plain text */
  printf("Encrypting:\n");
  bzero(ct1, sizeof(ct1));
  r = EVP_EncryptInit_ex(&ctx, EVP_aes_256_gcm(), NULL, key, iv);
  assert(r == 1, "EVP_EncryptInit_ex");
 
  ct_offset = 0;
 
  r = EVP_EncryptUpdate(&ctx, ct1 + ct_offset, &t, pt1, sizeof(pt1));
  ct_offset += t;
  assert(r == 1, "EVP_EncryptUpdate");
 
  r = EVP_EncryptFinal_ex(&ctx, ct1 + ct_offset, &t);
  ct_offset += t;
  assert(r == 1, "EVP_EncryptFinal_ex");
 
  assert(ct_offset == sizeof(ct1), "check ct1 len");
 
  /* Save tag */
  EVP_CIPHER_CTX_ctrl(&ctx, EVP_CTRL_GCM_GET_TAG, sizeof(tag), tag);

  /* Clean up */
  EVP_CIPHER_CTX_cleanup(&ctx);
 
  printf("cipher text: ");
  for (t=0; t<sizeof(ct1); t++) {
    printf("%02x", ct1[t]);
  }
  printf("\n\n");
 
  /* Decrypt the cipher text */
  printf("Decrypting:\n");
  bzero(pt2, sizeof(pt1));
  EVP_CIPHER_CTX_init(&ctx);
 
  r = EVP_DecryptInit_ex(&ctx, EVP_aes_256_gcm(), NULL, key, iv);
  assert(r == 1, "EVP_DecryptInit_ex");
 
  EVP_CIPHER_CTX_ctrl(&ctx, EVP_CTRL_GCM_SET_TAG, sizeof(tag), tag);
 
  pt2_offset = 0;
 
  r = EVP_DecryptUpdate(&ctx, pt2 + pt2_offset, &t, ct1, sizeof(ct1));
  pt2_offset += t;
  assert(r == 1, "EVP_DecryptUpdate");
 
  r = EVP_DecryptFinal_ex(&ctx, pt2 + pt2_offset, &t);
  pt2_offset += t;
  assert(r == 1, "EVP_DecryptFinal_ex");
 
  assert(pt2_offset == sizeof(pt1), "check pt2 len");
 
  r = 1;
  for (t=0; t<sizeof(pt1); t++) {
    r = r && (pt1[t] == pt2[t]);
  }
  assert(r == 1, "pt2 == pt1");
  printf("plain text: %s\n", pt2);  
  EVP_CIPHER_CTX_cleanup(&ctx);
  printf("\n"); 
 
  /* Truncate the cipher text */
  for (t=0; t<sizeof(ct2); t++) {
    ct2[t] = ct1[t];
  }

  /* Decrypt a subset */
  printf("Decrypting a subset, with EVP_EncryptInit_ex\n");
  bzero(pt2, sizeof(pt2));
  EVP_CIPHER_CTX_init(&ctx);
 
  r = EVP_EncryptInit_ex(&ctx, EVP_aes_256_gcm(), NULL, key, iv);
  assert(r == 1, "EVP_EncryptInit_ex");
 
  EVP_CIPHER_CTX_ctrl(&ctx, EVP_CTRL_GCM_SET_TAG, sizeof(tag), tag);
 
  pt2_offset = 0;
 
  r = EVP_DecryptUpdate(&ctx, pt2 + pt2_offset, &t, ct2, sizeof(ct2));
  pt2_offset += t;
  assert(r == 1, "EVP_DecryptUpdate");
 
  r = EVP_DecryptFinal_ex(&ctx, pt2 + pt2_offset, &t);
  pt2_offset += t; 
  assert(r == 1, "EVP_DecryptFinal_ex");
  printf("plain text: %s\n", pt2);
  EVP_CIPHER_CTX_cleanup(&ctx);
  printf("\n");
 
 
  /* Decrypt a subset, correctly this time */
  printf("Decrypting a subset, with EVP_DecryptInit_ex\n");
  bzero(pt2, sizeof(pt2));
  EVP_CIPHER_CTX_init(&ctx);
 
  r = EVP_DecryptInit_ex(&ctx, EVP_aes_256_gcm(), NULL, key, iv);
  assert(r == 1, "EVP_DecryptInit_ex");
 
  EVP_CIPHER_CTX_ctrl(&ctx, EVP_CTRL_GCM_SET_TAG, sizeof(tag), tag);
 
  pt2_offset = 0;
 
  r = EVP_DecryptUpdate(&ctx, pt2 + pt2_offset, &t, ct2, sizeof(ct2));
  pt2_offset += t;
  assert(r == 1, "EVP_DecryptUpdate");
 
  r = EVP_DecryptFinal_ex(&ctx, pt2 + pt2_offset, &t);
  pt2_offset += t;
  assert(r == 1, "EVP_DecryptFinal_ex");
  printf("plain text: %s\n", pt2); 
  EVP_CIPHER_CTX_cleanup(&ctx);
}