mirror of
https://github.com/cemu-project/Cemu.git
synced 2025-07-05 06:21:19 +12:00
Add initial ntag and nfc implementation
This commit is contained in:
parent
84e78088fb
commit
1c6b209692
21 changed files with 2927 additions and 47 deletions
406
src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.cpp
Normal file
406
src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.cpp
Normal file
|
@ -0,0 +1,406 @@
|
|||
#include "iosu_ccr_nfc.h"
|
||||
#include "Cafe/IOSU/kernel/iosu_kernel.h"
|
||||
#include "util/crypto/aes128.h"
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/hmac.h>
|
||||
|
||||
namespace iosu
|
||||
{
|
||||
namespace ccr_nfc
|
||||
{
|
||||
IOSMsgQueueId sCCRNFCMsgQueue;
|
||||
SysAllocator<iosu::kernel::IOSMessage, 0x20> sCCRNFCMsgQueueMsgBuffer;
|
||||
std::thread sCCRNFCThread;
|
||||
|
||||
constexpr uint8 sNfcKey[] = { 0xC1, 0x2B, 0x07, 0x10, 0xD7, 0x2C, 0xEB, 0x5D, 0x43, 0x49, 0xB7, 0x43, 0xE3, 0xCA, 0xD2, 0x24 };
|
||||
constexpr uint8 sNfcKeyIV[] = { 0x4F, 0xD3, 0x9A, 0x6E, 0x79, 0xFC, 0xEA, 0xAD, 0x99, 0x90, 0x4D, 0xB8, 0xEE, 0x38, 0xE9, 0xDB };
|
||||
|
||||
constexpr uint8 sUnfixedInfosMagicBytes[] = { 0x00, 0x00, 0xDB, 0x4B, 0x9E, 0x3F, 0x45, 0x27, 0x8F, 0x39, 0x7E, 0xFF, 0x9B, 0x4F, 0xB9, 0x93 };
|
||||
constexpr uint8 sLockedSecretMagicBytes[] = { 0xFD, 0xC8, 0xA0, 0x76, 0x94, 0xB8, 0x9E, 0x4C, 0x47, 0xD3, 0x7D, 0xE8, 0xCE, 0x5C, 0x74, 0xC1 };
|
||||
constexpr uint8 sUnfixedInfosString[] = { 0x75, 0x6E, 0x66, 0x69, 0x78, 0x65, 0x64, 0x20, 0x69, 0x6E, 0x66, 0x6F, 0x73, 0x00, 0x00, 0x00 };
|
||||
constexpr uint8 sLockedSecretString[] = { 0x6C, 0x6F, 0x63, 0x6B, 0x65, 0x64, 0x20, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x00, 0x00, 0x00 };
|
||||
|
||||
constexpr uint8 sLockedSecretHmacKey[] = { 0x7F, 0x75, 0x2D, 0x28, 0x73, 0xA2, 0x00, 0x17, 0xFE, 0xF8, 0x5C, 0x05, 0x75, 0x90, 0x4B, 0x6D };
|
||||
constexpr uint8 sUnfixedInfosHmacKey[] = { 0x1D, 0x16, 0x4B, 0x37, 0x5B, 0x72, 0xA5, 0x57, 0x28, 0xB9, 0x1D, 0x64, 0xB6, 0xA3, 0xC2, 0x05 };
|
||||
|
||||
uint8 sLockedSecretInternalKey[0x10];
|
||||
uint8 sLockedSecretInternalNonce[0x10];
|
||||
uint8 sLockedSecretInternalHmacKey[0x10];
|
||||
|
||||
uint8 sUnfixedInfosInternalKey[0x10];
|
||||
uint8 sUnfixedInfosInternalNonce[0x10];
|
||||
uint8 sUnfixedInfosInternalHmacKey[0x10];
|
||||
|
||||
sint32 __CCRNFCValidateCryptData(CCRNFCCryptData* data, uint32 size, bool validateOffsets)
|
||||
{
|
||||
if (!data)
|
||||
{
|
||||
return CCR_NFC_ERROR;
|
||||
}
|
||||
|
||||
if (size != sizeof(CCRNFCCryptData))
|
||||
{
|
||||
return CCR_NFC_ERROR;
|
||||
}
|
||||
|
||||
if (!validateOffsets)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Make sure all offsets are within bounds
|
||||
if (data->version == 0)
|
||||
{
|
||||
if (data->unfixedInfosHmacOffset < 0x1C9 && data->unfixedInfosOffset < 0x1C9 &&
|
||||
data->lockedSecretHmacOffset < 0x1C9 && data->lockedSecretOffset < 0x1C9 &&
|
||||
data->lockedSecretSize < 0x1C9 && data->unfixedInfosSize < 0x1C9)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else if (data->version == 2)
|
||||
{
|
||||
if (data->unfixedInfosHmacOffset < 0x21D && data->unfixedInfosOffset < 0x21D &&
|
||||
data->lockedSecretHmacOffset < 0x21D && data->lockedSecretOffset < 0x21D &&
|
||||
data->lockedSecretSize < 0x21D && data->unfixedInfosSize < 0x21D)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return CCR_NFC_ERROR;
|
||||
}
|
||||
|
||||
sint32 CCRNFCAESCTRCrypt(const uint8* key, const void* ivNonce, const void* inData, uint32_t inSize, void* outData, uint32_t outSize)
|
||||
{
|
||||
uint8_t tmpIv[0x10];
|
||||
memcpy(tmpIv, ivNonce, sizeof(tmpIv));
|
||||
|
||||
memcpy(outData, inData, inSize);
|
||||
AES128CTR_transform((uint8*)outData, outSize, (uint8*)key, tmpIv);
|
||||
return 0;
|
||||
}
|
||||
|
||||
sint32 __CCRNFCGenerateKey(const uint8* hmacKey, uint32 hmacKeySize, const uint8* name, uint32_t nameSize, const uint8* inData, uint32_t inSize, uint8* outData, uint32_t outSize)
|
||||
{
|
||||
if (nameSize != 0xe || outSize != 0x40)
|
||||
{
|
||||
return CCR_NFC_ERROR;
|
||||
}
|
||||
|
||||
// Create a buffer containing 2 counter bytes, the key name, and the key data
|
||||
uint8_t buffer[0x50];
|
||||
buffer[0] = 0;
|
||||
buffer[1] = 0;
|
||||
memcpy(buffer + 2, name, nameSize);
|
||||
memcpy(buffer + nameSize + 2, inData, inSize);
|
||||
|
||||
uint16_t counter = 0;
|
||||
while (outSize > 0)
|
||||
{
|
||||
// Set counter bytes and increment counter
|
||||
buffer[0] = (counter >> 8) & 0xFF;
|
||||
buffer[1] = counter & 0xFF;
|
||||
counter++;
|
||||
|
||||
uint32 dataSize = outSize;
|
||||
if (!HMAC(EVP_sha256(), hmacKey, hmacKeySize, buffer, sizeof(buffer), outData, &dataSize))
|
||||
{
|
||||
return CCR_NFC_ERROR;
|
||||
}
|
||||
|
||||
outSize -= 0x20;
|
||||
outData += 0x20;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
sint32 __CCRNFCGenerateInternalKeys(const CCRNFCCryptData* in, const uint8* keyGenSalt)
|
||||
{
|
||||
uint8_t lockedSecretBuffer[0x40] = { 0 };
|
||||
uint8_t unfixedInfosBuffer[0x40] = { 0 };
|
||||
uint8_t outBuffer[0x40] = { 0 };
|
||||
|
||||
// Fill the locked secret buffer
|
||||
memcpy(lockedSecretBuffer, sLockedSecretMagicBytes, sizeof(sLockedSecretMagicBytes));
|
||||
if (in->version == 0)
|
||||
{
|
||||
// For Version 0 this is the 16-byte Format Info
|
||||
memcpy(lockedSecretBuffer + 0x10, in->data + in->uuidOffset, 0x10);
|
||||
}
|
||||
else if (in->version == 2)
|
||||
{
|
||||
// For Version 2 this is 2 times the 7-byte UID + 1 check byte
|
||||
memcpy(lockedSecretBuffer + 0x10, in->data + in->uuidOffset, 8);
|
||||
memcpy(lockedSecretBuffer + 0x18, in->data + in->uuidOffset, 8);
|
||||
}
|
||||
else
|
||||
{
|
||||
return CCR_NFC_ERROR;
|
||||
}
|
||||
// Append key generation salt
|
||||
memcpy(lockedSecretBuffer + 0x20, keyGenSalt, 0x20);
|
||||
|
||||
// Generate the key output
|
||||
sint32 res = __CCRNFCGenerateKey(sLockedSecretHmacKey, sizeof(sLockedSecretHmacKey), sLockedSecretString, 0xe, lockedSecretBuffer, sizeof(lockedSecretBuffer), outBuffer, sizeof(outBuffer));
|
||||
if (res != 0)
|
||||
{
|
||||
return res;
|
||||
}
|
||||
|
||||
// Unpack the key buffer
|
||||
memcpy(sLockedSecretInternalKey, outBuffer, 0x10);
|
||||
memcpy(sLockedSecretInternalNonce, outBuffer + 0x10, 0x10);
|
||||
memcpy(sLockedSecretInternalHmacKey, outBuffer + 0x20, 0x10);
|
||||
|
||||
// Fill the unfixed infos buffer
|
||||
memcpy(unfixedInfosBuffer, in->data + in->seedOffset, 2);
|
||||
memcpy(unfixedInfosBuffer + 2, sUnfixedInfosMagicBytes + 2, 0xe);
|
||||
if (in->version == 0)
|
||||
{
|
||||
// For Version 0 this is the 16-byte Format Info
|
||||
memcpy(unfixedInfosBuffer + 0x10, in->data + in->uuidOffset, 0x10);
|
||||
}
|
||||
else if (in->version == 2)
|
||||
{
|
||||
// For Version 2 this is 2 times the 7-byte UID + 1 check byte
|
||||
memcpy(unfixedInfosBuffer + 0x10, in->data + in->uuidOffset, 8);
|
||||
memcpy(unfixedInfosBuffer + 0x18, in->data + in->uuidOffset, 8);
|
||||
}
|
||||
else
|
||||
{
|
||||
return CCR_NFC_ERROR;
|
||||
}
|
||||
// Append key generation salt
|
||||
memcpy(unfixedInfosBuffer + 0x20, keyGenSalt, 0x20);
|
||||
|
||||
// Generate the key output
|
||||
res = __CCRNFCGenerateKey(sUnfixedInfosHmacKey, sizeof(sUnfixedInfosHmacKey), sUnfixedInfosString, 0xe, unfixedInfosBuffer, sizeof(unfixedInfosBuffer), outBuffer, sizeof(outBuffer));
|
||||
if (res != 0)
|
||||
{
|
||||
return res;
|
||||
}
|
||||
|
||||
// Unpack the key buffer
|
||||
memcpy(sUnfixedInfosInternalKey, outBuffer, 0x10);
|
||||
memcpy(sUnfixedInfosInternalNonce, outBuffer + 0x10, 0x10);
|
||||
memcpy(sUnfixedInfosInternalHmacKey, outBuffer + 0x20, 0x10);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
sint32 __CCRNFCCryptData(const CCRNFCCryptData* in, CCRNFCCryptData* out, bool decrypt)
|
||||
{
|
||||
// Decrypt key generation salt
|
||||
uint8_t keyGenSalt[0x20];
|
||||
sint32 res = CCRNFCAESCTRCrypt(sNfcKey, sNfcKeyIV, in->data + in->keyGenSaltOffset, 0x20, keyGenSalt, sizeof(keyGenSalt));
|
||||
if (res != 0)
|
||||
{
|
||||
return res;
|
||||
}
|
||||
|
||||
// Prepare internal keys
|
||||
res = __CCRNFCGenerateInternalKeys(in, keyGenSalt);
|
||||
if (res != 0)
|
||||
{
|
||||
return res;
|
||||
}
|
||||
|
||||
if (decrypt)
|
||||
{
|
||||
// Only version 0 tags have an encrypted locked secret area
|
||||
if (in->version == 0)
|
||||
{
|
||||
res = CCRNFCAESCTRCrypt(sLockedSecretInternalKey, sLockedSecretInternalNonce, in->data + in->lockedSecretOffset, in->lockedSecretSize, out->data + in->lockedSecretOffset, in->lockedSecretSize);
|
||||
if (res != 0)
|
||||
{
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
// Decrypt unfxied infos
|
||||
res = CCRNFCAESCTRCrypt(sUnfixedInfosInternalKey, sUnfixedInfosInternalNonce, in->data + in->unfixedInfosOffset, in->unfixedInfosSize, out->data + in->unfixedInfosOffset, in->unfixedInfosSize);
|
||||
if (res != 0)
|
||||
{
|
||||
return res;
|
||||
}
|
||||
|
||||
// Verify HMACs
|
||||
uint8_t hmacBuffer[0x20];
|
||||
uint32 hmacLen = sizeof(hmacBuffer);
|
||||
|
||||
if (!HMAC(EVP_sha256(), sLockedSecretInternalHmacKey, sizeof(sLockedSecretInternalHmacKey), out->data + in->lockedSecretHmacOffset + 0x20, (in->dataSize - in->lockedSecretHmacOffset) - 0x20, hmacBuffer, &hmacLen))
|
||||
{
|
||||
return CCR_NFC_ERROR;
|
||||
}
|
||||
|
||||
if (memcmp(in->data + in->lockedSecretHmacOffset, hmacBuffer, 0x20) != 0)
|
||||
{
|
||||
return CCR_NFC_INVALID_LOCKED_SECRET;
|
||||
}
|
||||
|
||||
if (in->version == 0)
|
||||
{
|
||||
hmacLen = sizeof(hmacBuffer);
|
||||
res = HMAC(EVP_sha256(), sUnfixedInfosInternalHmacKey, sizeof(sUnfixedInfosInternalHmacKey), out->data + in->unfixedInfosHmacOffset + 0x20, (in->dataSize - in->unfixedInfosHmacOffset) - 0x20, hmacBuffer, &hmacLen) ? 0 : CCR_NFC_ERROR;
|
||||
}
|
||||
else
|
||||
{
|
||||
hmacLen = sizeof(hmacBuffer);
|
||||
res = HMAC(EVP_sha256(), sUnfixedInfosInternalHmacKey, sizeof(sUnfixedInfosInternalHmacKey), out->data + in->unfixedInfosHmacOffset + 0x21, (in->dataSize - in->unfixedInfosHmacOffset) - 0x21, hmacBuffer, &hmacLen) ? 0 : CCR_NFC_ERROR;
|
||||
}
|
||||
|
||||
if (memcmp(in->data + in->unfixedInfosHmacOffset, hmacBuffer, 0x20) != 0)
|
||||
{
|
||||
return CCR_NFC_INVALID_UNFIXED_INFOS;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
uint8_t hmacBuffer[0x20];
|
||||
uint32 hmacLen = sizeof(hmacBuffer);
|
||||
|
||||
if (!HMAC(EVP_sha256(), sLockedSecretInternalHmacKey, sizeof(sLockedSecretInternalHmacKey), out->data + in->lockedSecretHmacOffset + 0x20, (in->dataSize - in->lockedSecretHmacOffset) - 0x20, hmacBuffer, &hmacLen))
|
||||
{
|
||||
return CCR_NFC_ERROR;
|
||||
}
|
||||
|
||||
if (memcmp(in->data + in->lockedSecretHmacOffset, hmacBuffer, 0x20) != 0)
|
||||
{
|
||||
return CCR_NFC_INVALID_LOCKED_SECRET;
|
||||
}
|
||||
|
||||
// Only version 0 tags have an encrypted locked secret area
|
||||
if (in->version == 0)
|
||||
{
|
||||
uint32 hmacLen = 0x20;
|
||||
if (!HMAC(EVP_sha256(), sUnfixedInfosInternalHmacKey, sizeof(sUnfixedInfosInternalHmacKey), out->data + in->unfixedInfosHmacOffset + 0x20, (in->dataSize - in->unfixedInfosHmacOffset) - 0x20, out->data + in->unfixedInfosHmacOffset, &hmacLen))
|
||||
{
|
||||
return CCR_NFC_ERROR;
|
||||
}
|
||||
|
||||
res = CCRNFCAESCTRCrypt(sLockedSecretInternalKey, sLockedSecretInternalNonce, in->data + in->lockedSecretOffset, in->lockedSecretSize, out->data + in->lockedSecretOffset, in->lockedSecretSize);
|
||||
if (res != 0)
|
||||
{
|
||||
return res;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32 hmacLen = 0x20;
|
||||
if (!HMAC(EVP_sha256(), sUnfixedInfosInternalHmacKey, sizeof(sUnfixedInfosInternalHmacKey), out->data + in->unfixedInfosHmacOffset + 0x21, (in->dataSize - in->unfixedInfosHmacOffset) - 0x21, out->data + in->unfixedInfosHmacOffset, &hmacLen))
|
||||
{
|
||||
return CCR_NFC_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
res = CCRNFCAESCTRCrypt(sUnfixedInfosInternalKey, sUnfixedInfosInternalNonce, in->data + in->unfixedInfosOffset, in->unfixedInfosSize, out->data + in->unfixedInfosOffset, in->unfixedInfosSize);
|
||||
if (res != 0)
|
||||
{
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void CCRNFCThread()
|
||||
{
|
||||
iosu::kernel::IOSMessage msg;
|
||||
while (true)
|
||||
{
|
||||
IOS_ERROR error = iosu::kernel::IOS_ReceiveMessage(sCCRNFCMsgQueue, &msg, 0);
|
||||
cemu_assert(!IOS_ResultIsError(error));
|
||||
|
||||
// Check for system exit
|
||||
if (msg == 0xf00dd00d)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IPCCommandBody* cmd = MEMPTR<IPCCommandBody>(msg).GetPtr();
|
||||
if (cmd->cmdId == IPCCommandId::IOS_OPEN)
|
||||
{
|
||||
iosu::kernel::IOS_ResourceReply(cmd, IOS_ERROR_OK);
|
||||
}
|
||||
else if (cmd->cmdId == IPCCommandId::IOS_CLOSE)
|
||||
{
|
||||
iosu::kernel::IOS_ResourceReply(cmd, IOS_ERROR_OK);
|
||||
}
|
||||
else if (cmd->cmdId == IPCCommandId::IOS_IOCTL)
|
||||
{
|
||||
sint32 result;
|
||||
uint32 requestId = cmd->args[0];
|
||||
void* ptrIn = MEMPTR<void>(cmd->args[1]);
|
||||
uint32 sizeIn = cmd->args[2];
|
||||
void* ptrOut = MEMPTR<void>(cmd->args[3]);
|
||||
uint32 sizeOut = cmd->args[4];
|
||||
|
||||
if ((result = __CCRNFCValidateCryptData(static_cast<CCRNFCCryptData*>(ptrIn), sizeIn, true)) == 0 &&
|
||||
(result = __CCRNFCValidateCryptData(static_cast<CCRNFCCryptData*>(ptrOut), sizeOut, false)) == 0)
|
||||
{
|
||||
// Initialize outData with inData
|
||||
memcpy(ptrOut, ptrIn, sizeIn);
|
||||
|
||||
switch (requestId)
|
||||
{
|
||||
case 1: // encrypt
|
||||
result = __CCRNFCCryptData(static_cast<CCRNFCCryptData*>(ptrIn), static_cast<CCRNFCCryptData*>(ptrOut), false);
|
||||
break;
|
||||
case 2: // decrypt
|
||||
result = __CCRNFCCryptData(static_cast<CCRNFCCryptData*>(ptrIn), static_cast<CCRNFCCryptData*>(ptrOut), true);
|
||||
break;
|
||||
default:
|
||||
cemuLog_log(LogType::Force, "/dev/ccr_nfc: Unsupported IOCTL requestId");
|
||||
cemu_assert_suspicious();
|
||||
result = IOS_ERROR_INVALID;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
iosu::kernel::IOS_ResourceReply(cmd, static_cast<IOS_ERROR>(result));
|
||||
}
|
||||
else
|
||||
{
|
||||
cemuLog_log(LogType::Force, "/dev/ccr_nfc: Unsupported IPC cmdId");
|
||||
cemu_assert_suspicious();
|
||||
iosu::kernel::IOS_ResourceReply(cmd, IOS_ERROR_INVALID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class : public ::IOSUModule
|
||||
{
|
||||
void SystemLaunch() override
|
||||
{
|
||||
sCCRNFCMsgQueue = iosu::kernel::IOS_CreateMessageQueue(sCCRNFCMsgQueueMsgBuffer.GetPtr(), sCCRNFCMsgQueueMsgBuffer.GetCount());
|
||||
cemu_assert(!IOS_ResultIsError(static_cast<IOS_ERROR>(sCCRNFCMsgQueue)));
|
||||
|
||||
IOS_ERROR error = iosu::kernel::IOS_RegisterResourceManager("/dev/ccr_nfc", sCCRNFCMsgQueue);
|
||||
cemu_assert(!IOS_ResultIsError(error));
|
||||
|
||||
sCCRNFCThread = std::thread(CCRNFCThread);
|
||||
}
|
||||
|
||||
void SystemExit() override
|
||||
{
|
||||
if (sCCRNFCMsgQueue < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
iosu::kernel::IOS_SendMessage(sCCRNFCMsgQueue, 0xf00dd00d, 0);
|
||||
sCCRNFCThread.join();
|
||||
|
||||
iosu::kernel::IOS_DestroyMessageQueue(sCCRNFCMsgQueue);
|
||||
sCCRNFCMsgQueue = -1;
|
||||
}
|
||||
} sIOSUModuleCCRNFC;
|
||||
|
||||
IOSUModule* GetModule()
|
||||
{
|
||||
return &sIOSUModuleCCRNFC;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue