/* * Copyright (c) 2010 SURFnet bv * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. */ // TODO: Store EVP context in securely allocated memory /***************************************************************************** OSSLEVPSymmetricAlgorithm.cpp OpenSSL symmetric algorithm implementation *****************************************************************************/ #include "config.h" #include "OSSLEVPSymmetricAlgorithm.h" #include "OSSLUtil.h" #include "salloc.h" #include // Constructor OSSLEVPSymmetricAlgorithm::OSSLEVPSymmetricAlgorithm() { pCurCTX = NULL; maximumBytes = BN_new(); BN_one(maximumBytes); BN_set_negative(maximumBytes, 1); counterBytes = BN_new(); BN_zero(counterBytes); } // Destructor OSSLEVPSymmetricAlgorithm::~OSSLEVPSymmetricAlgorithm() { EVP_CIPHER_CTX_free(pCurCTX); BN_free(maximumBytes); BN_free(counterBytes); } // Encryption functions bool OSSLEVPSymmetricAlgorithm::encryptInit(const SymmetricKey* key, const SymMode::Type mode /* = SymMode::CBC */, const ByteString& IV /* = ByteString()*/, bool padding /* = true */, size_t counterBits /* = 0 */, const ByteString& aad /* = ByteString() */, size_t tagBytes /* = 0 */) { // Call the superclass initialiser if (!SymmetricAlgorithm::encryptInit(key, mode, IV, padding, counterBits, aad, tagBytes)) { return false; } // Check the IV if (mode != SymMode::GCM && (IV.size() > 0) && (IV.size() != getBlockSize())) { ERROR_MSG("Invalid IV size (%d bytes, expected %d bytes)", IV.size(), getBlockSize()); ByteString dummy; SymmetricAlgorithm::encryptFinal(dummy); return false; } ByteString iv; if (IV.size() > 0) { iv = IV; } else { iv.wipe(getBlockSize()); } // Check the counter bits if (counterBits > 0) { BIGNUM* counter = OSSL::byteString2bn(iv); BN_mask_bits(counter, counterBits); // Reverse the bits while (counterBits > 0) { counterBits--; if (BN_is_bit_set(counter, counterBits)) { BN_clear_bit(counter, counterBits); } else { BN_set_bit(counter, counterBits); } } // Set the maximum bytes BN_add_word(counter, 1); BN_mul_word(counter, getBlockSize()); BN_copy(maximumBytes, counter); BN_free(counter); BN_zero(counterBytes); } else { BN_one(maximumBytes); BN_set_negative(maximumBytes, 1); } // Determine the cipher class const EVP_CIPHER* cipher = getCipher(); if (cipher == NULL) { ERROR_MSG("Failed to initialise EVP encrypt operation"); ByteString dummy; SymmetricAlgorithm::encryptFinal(dummy); return false; } // Allocate the EVP context pCurCTX = EVP_CIPHER_CTX_new(); if (pCurCTX == NULL) { ERROR_MSG("Failed to allocate space for EVP_CIPHER_CTX"); ByteString dummy; SymmetricAlgorithm::encryptFinal(dummy); return false; } int rv; if (mode == SymMode::GCM) { rv = EVP_EncryptInit_ex(pCurCTX, cipher, NULL, NULL, NULL); if (rv) { EVP_CIPHER_CTX_ctrl(pCurCTX, EVP_CTRL_GCM_SET_IVLEN, iv.size(), NULL); rv = EVP_EncryptInit_ex(pCurCTX, NULL, NULL, (unsigned char*) currentKey->getKeyBits().const_byte_str(), iv.byte_str()); } } else { rv = EVP_EncryptInit(pCurCTX, cipher, (unsigned char*) currentKey->getKeyBits().const_byte_str(), iv.byte_str()); } if (!rv) { ERROR_MSG("Failed to initialise EVP encrypt operation: %s", ERR_error_string(ERR_get_error(), NULL)); EVP_CIPHER_CTX_free(pCurCTX); pCurCTX = NULL; ByteString dummy; SymmetricAlgorithm::encryptFinal(dummy); return false; } EVP_CIPHER_CTX_set_padding(pCurCTX, padding ? 1 : 0); if (mode == SymMode::GCM) { int outLen = 0; if (aad.size() && !EVP_EncryptUpdate(pCurCTX, NULL, &outLen, (unsigned char*) aad.const_byte_str(), aad.size())) { ERROR_MSG("Failed to update with AAD: %s", ERR_error_string(ERR_get_error(), NULL)); EVP_CIPHER_CTX_free(pCurCTX); pCurCTX = NULL; ByteString dummy; SymmetricAlgorithm::encryptFinal(dummy); return false; } } return true; } bool OSSLEVPSymmetricAlgorithm::encryptUpdate(const ByteString& data, ByteString& encryptedData) { if (!SymmetricAlgorithm::encryptUpdate(data, encryptedData)) { EVP_CIPHER_CTX_free(pCurCTX); pCurCTX = NULL; return false; } if (data.size() == 0) { encryptedData.resize(0); return true; } // Count number of bytes written if (!BN_is_negative(maximumBytes)) { BN_add_word(counterBytes, data.size()); } // Prepare the output block encryptedData.resize(data.size() + getBlockSize() - 1); int outLen = encryptedData.size(); if (!EVP_EncryptUpdate(pCurCTX, &encryptedData[0], &outLen, (unsigned char*) data.const_byte_str(), data.size())) { ERROR_MSG("EVP_EncryptUpdate failed: %s", ERR_error_string(ERR_get_error(), NULL)); EVP_CIPHER_CTX_free(pCurCTX); pCurCTX = NULL; ByteString dummy; SymmetricAlgorithm::encryptFinal(dummy); return false; } // Resize the output block encryptedData.resize(outLen); currentBufferSize -= outLen; return true; } bool OSSLEVPSymmetricAlgorithm::encryptFinal(ByteString& encryptedData) { SymMode::Type mode = currentCipherMode; size_t tagBytes = currentTagBytes; if (!SymmetricAlgorithm::encryptFinal(encryptedData)) { EVP_CIPHER_CTX_free(pCurCTX); pCurCTX = NULL; return false; } // Prepare the output block encryptedData.resize(getBlockSize()); int outLen = encryptedData.size(); if (!EVP_EncryptFinal(pCurCTX, &encryptedData[0], &outLen)) { ERROR_MSG("EVP_EncryptFinal failed: %s", ERR_error_string(ERR_get_error(), NULL)); EVP_CIPHER_CTX_free(pCurCTX); pCurCTX = NULL; return false; } // Resize the output block encryptedData.resize(outLen); if (mode == SymMode::GCM) { ByteString tag; tag.resize(tagBytes); EVP_CIPHER_CTX_ctrl(pCurCTX, EVP_CTRL_GCM_GET_TAG, tagBytes, &tag[0]); encryptedData += tag; } EVP_CIPHER_CTX_free(pCurCTX); pCurCTX = NULL; return true; } // Decryption functions bool OSSLEVPSymmetricAlgorithm::decryptInit(const SymmetricKey* key, const SymMode::Type mode /* = SymMode::CBC */, const ByteString& IV /* = ByteString() */, bool padding /* = true */, size_t counterBits /* = 0 */, const ByteString& aad /* = ByteString() */, size_t tagBytes /* = 0 */) { // Call the superclass initialiser if (!SymmetricAlgorithm::decryptInit(key, mode, IV, padding, counterBits, aad, tagBytes)) { return false; } // Check the IV if (mode != SymMode::GCM && (IV.size() > 0) && (IV.size() != getBlockSize())) { ERROR_MSG("Invalid IV size (%d bytes, expected %d bytes)", IV.size(), getBlockSize()); ByteString dummy; SymmetricAlgorithm::decryptFinal(dummy); return false; } ByteString iv; if (IV.size() > 0) { iv = IV; } else { iv.wipe(getBlockSize()); } // Check the counter bits if (counterBits > 0) { BIGNUM* counter = OSSL::byteString2bn(iv); BN_mask_bits(counter, counterBits); // Reverse the bits while (counterBits > 0) { counterBits--; if (BN_is_bit_set(counter, counterBits)) { BN_clear_bit(counter, counterBits); } else { BN_set_bit(counter, counterBits); } } // Set the maximum bytes BN_add_word(counter, 1); BN_mul_word(counter, getBlockSize()); BN_copy(maximumBytes, counter); BN_free(counter); BN_zero(counterBytes); } else { BN_one(maximumBytes); BN_set_negative(maximumBytes, 1); } // Determine the cipher class const EVP_CIPHER* cipher = getCipher(); if (cipher == NULL) { ERROR_MSG("Failed to initialise EVP decrypt operation"); ByteString dummy; SymmetricAlgorithm::decryptFinal(dummy); return false; } // Allocate the EVP context pCurCTX = EVP_CIPHER_CTX_new(); if (pCurCTX == NULL) { ERROR_MSG("Failed to allocate space for EVP_CIPHER_CTX"); ByteString dummy; SymmetricAlgorithm::decryptFinal(dummy); return false; } int rv; if (mode == SymMode::GCM) { rv = EVP_DecryptInit_ex(pCurCTX, cipher, NULL, NULL, NULL); if (rv) { EVP_CIPHER_CTX_ctrl(pCurCTX, EVP_CTRL_GCM_SET_IVLEN, iv.size(), NULL); rv = EVP_DecryptInit_ex(pCurCTX, NULL, NULL, (unsigned char*) currentKey->getKeyBits().const_byte_str(), iv.byte_str()); } } else { rv = EVP_DecryptInit(pCurCTX, cipher, (unsigned char*) currentKey->getKeyBits().const_byte_str(), iv.byte_str()); } if (!rv) { ERROR_MSG("Failed to initialise EVP decrypt operation: %s", ERR_error_string(ERR_get_error(), NULL)); EVP_CIPHER_CTX_free(pCurCTX); pCurCTX = NULL; ByteString dummy; SymmetricAlgorithm::decryptFinal(dummy); return false; } EVP_CIPHER_CTX_set_padding(pCurCTX, padding ? 1 : 0); if (mode == SymMode::GCM) { int outLen = 0; if (aad.size() && !EVP_DecryptUpdate(pCurCTX, NULL, &outLen, (unsigned char*) aad.const_byte_str(), aad.size())) { ERROR_MSG("Failed to update with AAD: %s", ERR_error_string(ERR_get_error(), NULL)); EVP_CIPHER_CTX_free(pCurCTX); pCurCTX = NULL; ByteString dummy; SymmetricAlgorithm::decryptFinal(dummy); return false; } } return true; } bool OSSLEVPSymmetricAlgorithm::decryptUpdate(const ByteString& encryptedData, ByteString& data) { if (!SymmetricAlgorithm::decryptUpdate(encryptedData, data)) { EVP_CIPHER_CTX_free(pCurCTX); pCurCTX = NULL; return false; } // AEAD ciphers should not return decrypted data until final is called if (currentCipherMode == SymMode::GCM) { data.resize(0); return true; } // Count number of bytes written if (!BN_is_negative(maximumBytes)) { BN_add_word(counterBytes, encryptedData.size()); } // Prepare the output block data.resize(encryptedData.size() + getBlockSize()); int outLen = data.size(); DEBUG_MSG("Decrypting %d bytes into buffer of %d bytes", encryptedData.size(), data.size()); if (!EVP_DecryptUpdate(pCurCTX, &data[0], &outLen, (unsigned char*) encryptedData.const_byte_str(), encryptedData.size())) { ERROR_MSG("EVP_DecryptUpdate failed: %s", ERR_error_string(ERR_get_error(), NULL)); EVP_CIPHER_CTX_free(pCurCTX); pCurCTX = NULL; ByteString dummy; SymmetricAlgorithm::decryptFinal(dummy); return false; } DEBUG_MSG("Decrypt returned %d bytes of data", outLen); // Resize the output block data.resize(outLen); currentBufferSize -= outLen; return true; } bool OSSLEVPSymmetricAlgorithm::decryptFinal(ByteString& data) { SymMode::Type mode = currentCipherMode; size_t tagBytes = currentTagBytes; ByteString aeadBuffer = currentAEADBuffer; if (!SymmetricAlgorithm::decryptFinal(data)) { EVP_CIPHER_CTX_free(pCurCTX); pCurCTX = NULL; return false; } data.resize(0); if (mode == SymMode::GCM) { // Check buffer size if (aeadBuffer.size() < tagBytes) { ERROR_MSG("Tag bytes (%d) does not fit in AEAD buffer (%d)", tagBytes, aeadBuffer.size()); EVP_CIPHER_CTX_free(pCurCTX); pCurCTX = NULL; return false; } // Set the tag EVP_CIPHER_CTX_ctrl(pCurCTX, EVP_CTRL_GCM_SET_TAG, tagBytes, &aeadBuffer[aeadBuffer.size()-tagBytes]); // Prepare the output block data.resize(aeadBuffer.size() - tagBytes + getBlockSize()); int outLen = data.size(); if (!EVP_DecryptUpdate(pCurCTX, &data[0], &outLen, (unsigned char*) aeadBuffer.const_byte_str(), aeadBuffer.size() - tagBytes)) { ERROR_MSG("EVP_DecryptUpdate failed: %s", ERR_error_string(ERR_get_error(), NULL)); EVP_CIPHER_CTX_free(pCurCTX); pCurCTX = NULL; return false; } data.resize(outLen); } // Prepare the output block int initialSize = data.size(); data.resize(initialSize + getBlockSize()); int outLen = data.size() - initialSize; int rv; if (!(rv = EVP_DecryptFinal(pCurCTX, &data[initialSize], &outLen))) { ERROR_MSG("EVP_DecryptFinal failed (0x%08X): %s", rv, ERR_error_string(ERR_get_error(), NULL)); EVP_CIPHER_CTX_free(pCurCTX); pCurCTX = NULL; return false; } // Resize the output block data.resize(initialSize + outLen); EVP_CIPHER_CTX_free(pCurCTX); pCurCTX = NULL; return true; } // Check if more bytes of data can be encrypted bool OSSLEVPSymmetricAlgorithm::checkMaximumBytes(unsigned long bytes) { if (BN_is_negative(maximumBytes)) return true; BIGNUM* bigNum = BN_new(); BN_copy(bigNum, counterBytes); BN_add_word(bigNum, bytes); bool rv = false; if (BN_cmp(maximumBytes, bigNum) >= 0) rv = true; BN_free(bigNum); return rv; }