Files
provisioning_sdk_source/common/ecies_crypto.cc
2020-09-21 15:54:27 -07:00

240 lines
8.1 KiB
C++

////////////////////////////////////////////////////////////////////////////////
// Copyright 2019 Google LLC.
//
// This software is licensed under the terms defined in the Widevine Master
// License Agreement. For a copy of this agreement, please contact
// widevine-licensing@google.com.
////////////////////////////////////////////////////////////////////////////////
#include "common/ecies_crypto.h"
#include <memory>
#include "glog/logging.h"
#include "absl/strings/escaping.h"
#include "openssl/ec.h"
#include "common/aes_cbc_util.h"
#include "common/crypto_util.h"
#include "common/ec_key.h"
#include "common/ec_util.h"
#include "common/openssl_util.h"
namespace {
const char kZeroIv[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
const size_t kMacSizeBytes = 32;
const size_t kMasterKeySizeBytes = 32;
const size_t kEncryptionKeySizeBytes = kMasterKeySizeBytes;
const size_t kSigningKeySizeBytes = kMasterKeySizeBytes;
const size_t kEncryptionKeySizeBits = kEncryptionKeySizeBytes * 8;
const size_t kSigningKeySizeBits = kSigningKeySizeBytes * 8;
const char kWrappingKeyLabel[] = "ECIESEncryptionLabel";
const char kSigningKeyLabel[] = "ECIESSigningLabel";
bool DeriveWrappingAndSigningKeys(const std::string& key,
const std::string& context,
std::string* wrapping_key,
std::string* signing_key) {
if (wrapping_key == nullptr || signing_key == nullptr) return false;
if (key.size() != kMasterKeySizeBytes) {
LOG(ERROR) << "Invalid master key size";
return false;
}
*wrapping_key = widevine::crypto_util::DeriveKey(
key, kWrappingKeyLabel, context, kEncryptionKeySizeBits);
if (wrapping_key->empty()) {
LOG(WARNING) << "Failed to derive encryption key.";
return false;
}
*signing_key = widevine::crypto_util::DeriveKey(
key, kSigningKeyLabel, context, kSigningKeySizeBits);
if (signing_key->empty()) {
LOG(WARNING) << "Failed to derive sigining key.";
return false;
}
return true;
}
} // anonymous namespace
namespace widevine {
EciesEncryptor::EciesEncryptor(std::unique_ptr<ECPublicKey> public_key,
ECKeySource* key_source)
: public_key_(std::move(public_key)), key_source_(key_source) {}
std::unique_ptr<EciesEncryptor> EciesEncryptor::Create(
const std::string& serialized_public_key, ECKeySource* key_source) {
if (key_source == nullptr) {
return nullptr;
}
std::unique_ptr<ECPublicKey> ec_key =
ECPublicKey::Create(serialized_public_key);
if (ec_key == nullptr) {
return nullptr;
}
std::unique_ptr<EciesEncryptor> encryptor(
new EciesEncryptor(std::move(ec_key), key_source));
return encryptor;
}
bool EciesEncryptor::Encrypt(const std::string& plaintext,
const std::string& context,
std::string* ecies_message) const {
if (ecies_message == nullptr) {
LOG(ERROR) << "ecies_message cannot be null";
return false;
}
std::string serialized_private_key;
std::string serialized_public_key;
if (!key_source_->GetECKey(public_key_->Curve(), &serialized_private_key,
&serialized_public_key)) {
LOG(WARNING) << "Failed to get key pair from key source.";
return false;
}
// Convert the ephemeral public key to an encoded EC point.
std::unique_ptr<ECPublicKey> ephemeral_public_key =
ECPublicKey::Create(serialized_public_key);
std::string encoded_public_key;
if (!ephemeral_public_key->GetPointEncodedKey(&encoded_public_key)) {
LOG(ERROR) << "Could not encode the public key. ";
return false;
}
// This condition is just an indication that the serialized_public_key doesn't
// match our size expectations. It shouldn't block the encryption operation.
size_t expected_size = ec_util::GetPublicKeyPointSize(public_key_->Curve());
if (encoded_public_key.size() != expected_size) {
LOG(WARNING) << "Unexpected key size for public key. "
<< "Curve " << public_key_->Curve() << ". Expected "
<< expected_size << ". Found " << encoded_public_key.size()
<< ".";
}
std::unique_ptr<ECPrivateKey> ephemeral_private_key =
ECPrivateKey::Create(serialized_private_key);
std::string session_key;
if (!ephemeral_private_key->DeriveSharedSessionKey(*public_key_,
&session_key)) {
LOG(WARNING) << "Failed to derive shared session key.";
return false;
}
std::string encryption_key;
std::string signing_key;
if (!DeriveWrappingAndSigningKeys(session_key, context, &encryption_key,
&signing_key)) {
return false;
}
std::string zero_iv(kZeroIv, sizeof(kZeroIv));
std::string ciphertext =
crypto_util::EncryptAesCbc(encryption_key, zero_iv, plaintext);
if (ciphertext.empty()) {
LOG(WARNING) << "Failed to encrypt plaintext.";
return false;
}
std::string signature =
crypto_util::CreateSignatureHmacSha256(signing_key, ciphertext);
if (signature.empty()) {
LOG(WARNING) << "Failed to sign plaintext.";
return false;
}
*ecies_message = encoded_public_key + ciphertext + signature;
return true;
}
std::unique_ptr<EciesDecryptor> EciesDecryptor::Create(
const std::string& serialized_private_key) {
std::unique_ptr<ECPrivateKey> ec_key =
ECPrivateKey::Create(serialized_private_key);
if (ec_key == nullptr) {
return nullptr;
}
std::unique_ptr<EciesDecryptor> decryptor(
new EciesDecryptor(std::move(ec_key)));
if (decryptor == nullptr) {
LOG(ERROR) << "Failed to create EciesDecryptor.";
}
return decryptor;
}
EciesDecryptor::EciesDecryptor(std::unique_ptr<ECPrivateKey> private_key)
: private_key_(std::move(private_key)) {}
bool EciesDecryptor::Decrypt(const std::string& ecies_message,
const std::string& context,
std::string* plaintext) const {
if (plaintext == nullptr) {
LOG(ERROR) << "plaintext cannot be null";
return false;
}
// Check the minimum std::string size.
size_t key_size = ec_util::GetPublicKeyPointSize(private_key_->Curve());
if (key_size + kMacSizeBytes > ecies_message.size()) {
LOG(ERROR) << "The size of the message is too small. Expected > "
<< key_size + kMacSizeBytes << ". found "
<< ecies_message.size();
return false;
}
std::string ciphertext = ecies_message.substr(
key_size, ecies_message.size() - kMacSizeBytes - key_size);
std::string signature =
ecies_message.substr(ecies_message.size() - kMacSizeBytes, kMacSizeBytes);
std::unique_ptr<ECPublicKey> public_key = ECPublicKey::CreateFromKeyPoint(
private_key_->Curve(), ecies_message.substr(0, key_size));
if (public_key == nullptr) {
LOG(WARNING) << "Failed to deserialize public key.";
return false;
}
std::string session_key;
if (!private_key_->DeriveSharedSessionKey(*public_key, &session_key)) {
LOG(WARNING) << "Failed to derive shared session key.";
return false;
}
std::string encryption_key;
std::string signing_key;
if (!DeriveWrappingAndSigningKeys(session_key, context, &encryption_key,
&signing_key)) {
LOG(WARNING) << "Failed to derive shared wrapping and signing keys.";
return false;
}
if (!crypto_util::VerifySignatureHmacSha256(signing_key, signature,
ciphertext)) {
LOG(WARNING) << "Failed to verify signature on ciphertext.";
return false;
}
std::string zero_iv(kZeroIv, sizeof(kZeroIv));
// In theory, we should be able to decrypt a cipher block that was originally
// the empty string. But DecryptAesCbc uses an empty std::string to indicate an
// error. This means that we can't distinguish between an error and correctly
// decrypted empty string.
*plaintext = crypto_util::DecryptAesCbc(encryption_key, zero_iv, ciphertext);
if (plaintext->empty()) {
LOG(WARNING) << "Failed to decrypt plaintext.";
return false;
}
return true;
}
} // namespace widevine.