blob: bd4443f971c152f62f4f868c32eb048b921fcfa3 [file] [log] [blame]
/********************************************************************
* 2014 -
* open source under Apache License Version 2.0
********************************************************************/
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 "CryptoCodec.h"
#include "Logger.h"
using namespace Hdfs::Internal;
namespace Hdfs {
//copy from java HDFS code
std::string CryptoCodec::calculateIV(const std::string& initIV, unsigned long counter) {
char IV[initIV.length()];
int i = initIV.length(); // IV length
int j = 0; // counter bytes index
unsigned int sum = 0;
while (i-- > 0) {
// (sum >>> Byte.SIZE) is the carry for addition
sum = (initIV[i] & 0xff) + (sum >> 8);
if (j++ < 8) { // Big-endian, and long is 8 bytes length
sum += (char) counter & 0xff;
counter >>= 8;
}
IV[i] = (char) sum;
}
return std::string(IV, initIV.length());
}
CryptoCodec::CryptoCodec(FileEncryptionInfo *encryptionInfo, shared_ptr<KmsClientProvider> kcp, int32_t bufSize) :
encryptionInfo(encryptionInfo), kcp(kcp), bufSize(bufSize)
{
// Init global status
ERR_load_crypto_strings();
OpenSSL_add_all_algorithms();
OPENSSL_config(NULL);
// Create cipher context
cipherCtx = EVP_CIPHER_CTX_new();
cipher = NULL;
padding = 0;
counter = 0;
is_init = false;
}
CryptoCodec::~CryptoCodec()
{
if (cipherCtx)
EVP_CIPHER_CTX_free(cipherCtx);
}
std::string CryptoCodec::getDecryptedKeyFromKms()
{
ptree map = kcp->decryptEncryptedKey(*encryptionInfo);
std::string key;
try {
key = map.get < std::string > ("material");
} catch (...) {
THROW(HdfsIOException, "CryptoCodec : Can not get key from kms.");
}
int rem = key.length() % 4;
if (rem) {
rem = 4 - rem;
while (rem != 0) {
key = key + "=";
rem--;
}
}
std::replace(key.begin(), key.end(), '-', '+');
std::replace(key.begin(), key.end(), '_', '/');
LOG(DEBUG3, "CryptoCodec : getDecryptedKeyFromKms material is :%s", key.c_str());
key = KmsClientProvider::base64Decode(key);
return key;
}
int CryptoCodec::init(CryptoMethod crypto_method, int64_t stream_offset) {
// Check CryptoCodec init or not.
if (is_init)
return 0;
// Get decrypted key from KMS.
decryptedKey = getDecryptedKeyFromKms();
// Select cipher method based on the decrypted key length.
AlgorithmBlockSize = decryptedKey.length();
if (AlgorithmBlockSize == KEY_LENGTH_256) {
cipher = EVP_aes_256_ctr();
} else if (AlgorithmBlockSize == KEY_LENGTH_128) {
cipher = EVP_aes_128_ctr();
} else {
LOG(WARNING, "CryptoCodec : Invalid key length.");
return -1;
}
is_init = true;
// Calculate iv and counter in order to init cipher context with cipher method. Default value is 0.
if ((resetStreamOffset(crypto_method, stream_offset)) < 0) {
is_init = false;
return -1;
}
LOG(DEBUG3, "CryptoCodec init success, length of the decrypted key is : %llu, crypto method is : %d", AlgorithmBlockSize, crypto_method);
return 1;
}
int CryptoCodec::resetStreamOffset(CryptoMethod crypto_method, int64_t stream_offset) {
// Check CryptoCodec init or not.
if (is_init == false)
return -1;
// Calculate new IV when appending an existed file.
std::string iv = encryptionInfo->getIv();
if (stream_offset > 0) {
counter = stream_offset / AlgorithmBlockSize;
padding = stream_offset % AlgorithmBlockSize;
iv = this->calculateIV(iv, counter);
}
// Judge the crypto method is encrypt or decrypt.
int enc = (method == CryptoMethod::ENCRYPT) ? 1 : 0;
// Init cipher context with cipher method.
if (!EVP_CipherInit_ex(cipherCtx, cipher, NULL,
(const unsigned char *) decryptedKey.c_str(), (const unsigned char *) iv.c_str(),
enc)) {
LOG(WARNING, "EVP_CipherInit_ex failed");
return -1;
}
// AES/CTR/NoPadding, set padding to 0.
EVP_CIPHER_CTX_set_padding(cipherCtx, 0);
return 1;
}
std::string CryptoCodec::cipher_wrap(const char * buffer, int64_t size) {
if (!is_init)
THROW(InvalidParameter, "CryptoCodec isn't init");
int offset = 0;
int remaining = size;
int len = 0;
int ret = 0;
std::string in_buf(buffer,size);
std::string out_buf(size, 0);
//set necessary padding when appending a existed file
if (padding > 0) {
in_buf.insert(0, padding, 0);
out_buf.resize(padding+size);
remaining += padding;
}
// If the encode/decode buffer size larger than crypto buffer size, encode/decode buffer one by one
while (remaining > bufSize) {
ret = EVP_CipherUpdate(cipherCtx, (unsigned char *) &out_buf[offset], &len,
(const unsigned char *)in_buf.data() + offset, bufSize);
if (!ret) {
std::string err = ERR_lib_error_string(ERR_get_error());
THROW(HdfsIOException, "CryptoCodec : cipher_wrap AES data failed:%s, crypto_method:%d", err.c_str(), method);
}
offset += len;
remaining -= len;
LOG(DEBUG3, "CryptoCodec : EVP_CipherUpdate successfully, len:%d", len);
}
if (remaining) {
ret = EVP_CipherUpdate(cipherCtx, (unsigned char *) &out_buf[offset], &len,
(const unsigned char *) in_buf.data() + offset, remaining);
if (!ret) {
std::string err = ERR_lib_error_string(ERR_get_error());
THROW(HdfsIOException, "CryptoCodec : cipher_wrap AES data failed:%s, crypto_method:%d", err.c_str(), method);
}
}
//cut off padding when necessary
if (padding > 0) {
out_buf.erase(0, padding);
padding = 0;
}
return out_buf;
}
}