95 lines
3.4 KiB
C++
95 lines
3.4 KiB
C++
// Copyright 2017 Citra Emulator Project
|
|
// Licensed under GPLv2 or any later version
|
|
// Refer to the license.txt file included.
|
|
|
|
#include <algorithm>
|
|
#include <cryptopp/aes.h>
|
|
#include <cryptopp/ccm.h>
|
|
#include <cryptopp/cryptlib.h>
|
|
#include <cryptopp/filters.h>
|
|
#include "common/alignment.h"
|
|
#include "common/logging/log.h"
|
|
#include "core/hw/aes/ccm.h"
|
|
#include "core/hw/aes/key.h"
|
|
|
|
namespace HW {
|
|
namespace AES {
|
|
|
|
namespace {
|
|
|
|
// 3DS uses a non-standard AES-CCM algorithm, so we need to derive a sub class from the standard one
|
|
// and override with the non-standard part.
|
|
using CryptoPP::lword;
|
|
using CryptoPP::AES;
|
|
using CryptoPP::CCM_Final;
|
|
using CryptoPP::CCM_Base;
|
|
template <bool T_IsEncryption>
|
|
class CCM_3DSVariant_Final : public CCM_Final<AES, CCM_MAC_SIZE, T_IsEncryption> {
|
|
public:
|
|
void UncheckedSpecifyDataLengths(lword header_length, lword message_length,
|
|
lword footer_length) override {
|
|
// 3DS uses the aligned size to generate B0 for authentication, instead of the original size
|
|
lword aligned_message_length = Common::AlignUp(message_length, AES_BLOCK_SIZE);
|
|
CCM_Base::UncheckedSpecifyDataLengths(header_length, aligned_message_length, footer_length);
|
|
CCM_Base::m_messageLength = message_length; // restore the actual message size
|
|
}
|
|
};
|
|
|
|
class CCM_3DSVariant {
|
|
public:
|
|
using Encryption = CCM_3DSVariant_Final<true>;
|
|
using Decryption = CCM_3DSVariant_Final<false>;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
std::vector<u8> EncryptSignCCM(const std::vector<u8>& pdata, const CCMNonce& nonce,
|
|
size_t slot_id) {
|
|
if (!IsNormalKeyAvailable(slot_id)) {
|
|
LOG_ERROR(HW_AES, "Key slot %d not available. Will use zero key.", slot_id);
|
|
}
|
|
const AESKey normal = GetNormalKey(slot_id);
|
|
std::vector<u8> cipher(pdata.size() + CCM_MAC_SIZE);
|
|
|
|
try {
|
|
CCM_3DSVariant::Encryption e;
|
|
e.SetKeyWithIV(normal.data(), AES_BLOCK_SIZE, nonce.data(), CCM_NONCE_SIZE);
|
|
e.SpecifyDataLengths(0, pdata.size(), 0);
|
|
CryptoPP::ArraySource as(pdata.data(), pdata.size(), true,
|
|
new CryptoPP::AuthenticatedEncryptionFilter(
|
|
e, new CryptoPP::ArraySink(cipher.data(), cipher.size())));
|
|
} catch (const CryptoPP::Exception& e) {
|
|
LOG_ERROR(HW_AES, "FAILED with: %s", e.what());
|
|
}
|
|
return cipher;
|
|
}
|
|
|
|
std::vector<u8> DecryptVerifyCCM(const std::vector<u8>& cipher, const CCMNonce& nonce,
|
|
size_t slot_id) {
|
|
if (!IsNormalKeyAvailable(slot_id)) {
|
|
LOG_ERROR(HW_AES, "Key slot %d not available. Will use zero key.", slot_id);
|
|
}
|
|
const AESKey normal = GetNormalKey(slot_id);
|
|
const std::size_t pdata_size = cipher.size() - CCM_MAC_SIZE;
|
|
std::vector<u8> pdata(pdata_size);
|
|
|
|
try {
|
|
CCM_3DSVariant::Decryption d;
|
|
d.SetKeyWithIV(normal.data(), AES_BLOCK_SIZE, nonce.data(), CCM_NONCE_SIZE);
|
|
d.SpecifyDataLengths(0, pdata_size, 0);
|
|
CryptoPP::AuthenticatedDecryptionFilter df(
|
|
d, new CryptoPP::ArraySink(pdata.data(), pdata_size));
|
|
CryptoPP::ArraySource as(cipher.data(), cipher.size(), true, new CryptoPP::Redirector(df));
|
|
if (!df.GetLastResult()) {
|
|
LOG_ERROR(HW_AES, "FAILED");
|
|
return {};
|
|
}
|
|
} catch (const CryptoPP::Exception& e) {
|
|
LOG_ERROR(HW_AES, "FAILED with: %s", e.what());
|
|
return {};
|
|
}
|
|
return pdata;
|
|
}
|
|
|
|
} // namespace AES
|
|
} // namespace HW
|