1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-31 03:00:05 +00:00

UniqueID: Use stable SMBIOS fields to generate ID on Windows

This commit adds fixes for generating unique hardware IDs on Windows. The SMBIOS is parsed to generate a unique ID based on hardware factors of the local machine.
This commit is contained in:
Oliver James 2023-04-21 18:25:31 +01:00 committed by Aymeric
parent 66fa787b01
commit ea917c596d
5 changed files with 441 additions and 30 deletions

View file

@ -659,28 +659,180 @@ String SystemStats::getDisplayLanguage()
return languagesBuffer.data();
}
static constexpr DWORD generateProviderID (const char* string)
{
return (DWORD) string[0] << 0x18
| (DWORD) string[1] << 0x10
| (DWORD) string[2] << 0x08
| (DWORD) string[3] << 0x00;
}
static std::optional<std::vector<std::byte>> readSMBIOSData()
{
const auto sig = generateProviderID ("RSMB");
const auto id = generateProviderID ("RSDT");
if (const auto bufLen = GetSystemFirmwareTable (sig, id, nullptr, 0); bufLen > 0)
{
std::vector<std::byte> buffer;
buffer.resize (bufLen);
if (GetSystemFirmwareTable (sig, id, buffer.data(), bufLen) == buffer.size())
return std::make_optional (std::move (buffer));
}
return {};
}
String getLegacyUniqueDeviceID()
{
if (const auto dump = readSMBIOSData())
{
uint64_t hash = 0;
const auto start = dump->data();
const auto end = start + jmin (1024, (int) dump->size());
for (auto dataPtr = start; dataPtr != end; ++dataPtr)
hash = hash * (uint64_t) 101 + (uint8_t) *dataPtr;
return String (hash);
}
return {};
}
String SystemStats::getUniqueDeviceID()
{
#define PROVIDER(string) (DWORD) (string[0] << 24 | string[1] << 16 | string[2] << 8 | string[3])
auto bufLen = GetSystemFirmwareTable (PROVIDER ("RSMB"), PROVIDER ("RSDT"), nullptr, 0);
if (bufLen > 0)
if (const auto smbiosBuffer = readSMBIOSData())
{
HeapBlock<uint8_t> buffer { bufLen };
GetSystemFirmwareTable (PROVIDER ("RSMB"), PROVIDER ("RSDT"), (void*) buffer.getData(), bufLen);
return [&]
#pragma pack (push, 1)
struct RawSMBIOSData
{
uint64_t hash = 0;
const auto start = buffer.getData();
const auto end = start + jmin (1024, (int) bufLen);
uint8_t unused[4];
uint32_t length;
};
for (auto dataPtr = start; dataPtr != end; ++dataPtr)
hash = hash * (uint64_t) 101 + *dataPtr;
struct SMBIOSHeader
{
uint8_t id;
uint8_t length;
uint16_t handle;
};
#pragma pack (pop)
return String (hash);
}();
String uuid;
const auto* asRawSMBIOSData = unalignedPointerCast<const RawSMBIOSData*> (smbiosBuffer->data());
Span<const std::byte> content (smbiosBuffer->data() + sizeof (RawSMBIOSData), asRawSMBIOSData->length);
while (! content.empty())
{
const auto* header = unalignedPointerCast<const SMBIOSHeader*> (content.data());
const auto* stringTable = unalignedPointerCast<const char*> (content.data() + header->length);
std::vector<const char*> strings;
// Each table comprises a struct and a varying number of null terminated
// strings. The string section is delimited by a pair of null terminators.
// Some fields in the header are indices into the string table.
const auto sizeofStringTable = [stringTable, &strings, &content]
{
size_t tableLen = 0;
while (tableLen < content.size())
{
const auto* str = stringTable + tableLen;
const auto n = strlen (str);
if (n == 0)
break;
strings.push_back (str);
tableLen += n + 1;
}
return jmax (tableLen, (size_t) 1) + 1;
}();
const auto stringFromOffset = [&content, &strings = std::as_const (strings)] (size_t byteOffset)
{
if (const auto index = std::to_integer<size_t> (content[byteOffset]); 0 < index && index <= strings.size())
return strings[index - 1];
return "";
};
enum
{
systemManufacturer = 0x04,
systemProductName = 0x05,
systemSerialNumber = 0x07,
systemUUID = 0x08, // 16byte UUID. Can be all 0xFF or all 0x00. Might be user changeable.
systemSKU = 0x19,
systemFamily = 0x1a,
baseboardManufacturer = 0x04,
baseboardProduct = 0x05,
baseboardVersion = 0x06,
baseboardSerialNumber = 0x07,
baseboardAssetTag = 0x08,
processorManufacturer = 0x07,
processorVersion = 0x10,
processorAssetTag = 0x21,
processorPartNumber = 0x22
};
switch (header->id)
{
case 1: // System
{
uuid += stringFromOffset (systemManufacturer);
uuid += "\n";
uuid += stringFromOffset (systemProductName);
uuid += "\n";
char hexBuf[(16 * 2) + 1]{};
const auto* src = content.data() + systemUUID;
for (auto i = 0; i != 16; ++i)
snprintf (hexBuf + 2 * i, 3, "%02hhX", src[i]);
uuid += hexBuf;
uuid += "\n";
break;
}
case 2: // Baseboard
uuid += stringFromOffset (baseboardManufacturer);
uuid += "\n";
uuid += stringFromOffset (baseboardProduct);
uuid += "\n";
uuid += stringFromOffset (baseboardVersion);
uuid += "\n";
uuid += stringFromOffset (baseboardSerialNumber);
uuid += "\n";
uuid += stringFromOffset (baseboardAssetTag);
uuid += "\n";
break;
case 4: // Processor
uuid += stringFromOffset (processorManufacturer);
uuid += "\n";
uuid += stringFromOffset (processorVersion);
uuid += "\n";
uuid += stringFromOffset (processorAssetTag);
uuid += "\n";
uuid += stringFromOffset (processorPartNumber);
uuid += "\n";
break;
}
const auto increment = header->length + sizeofStringTable;
content = Span (content.data() + increment, content.size() - increment);
}
return String (uuid.hashCode64());
}
// Please tell someone at JUCE if this occurs

View file

@ -60,24 +60,64 @@ String SystemStats::getJUCEVersion()
StringArray SystemStats::getDeviceIdentifiers()
{
for (const auto flag : { MachineIdFlags::fileSystemId, MachineIdFlags::macAddresses })
if (auto ids = getMachineIdentifiers (flag); ! ids.isEmpty())
return ids;
jassertfalse; // Failed to create any IDs!
return {};
}
String getLegacyUniqueDeviceID();
StringArray SystemStats::getMachineIdentifiers (MachineIdFlags flags)
{
auto macAddressProvider = [] (StringArray& arr)
{
for (const auto& mac : MACAddress::getAllAddresses())
arr.add (mac.toString());
};
auto fileSystemProvider = [] (StringArray& arr)
{
#if JUCE_WINDOWS
File f (File::getSpecialLocation (File::windowsSystemDirectory));
#else
File f ("~");
#endif
if (auto num = f.getFileIdentifier())
arr.add (String::toHexString ((int64) num));
};
auto legacyIdProvider = [] ([[maybe_unused]] StringArray& arr)
{
#if JUCE_WINDOWS
arr.add (getLegacyUniqueDeviceID());
#endif
};
auto uniqueIdProvider = [] (StringArray& arr)
{
arr.add (getUniqueDeviceID());
};
struct Provider { MachineIdFlags flag; void (*func) (StringArray&); };
static const Provider providers[] =
{
{ MachineIdFlags::macAddresses, macAddressProvider },
{ MachineIdFlags::fileSystemId, fileSystemProvider },
{ MachineIdFlags::legacyUniqueId, legacyIdProvider },
{ MachineIdFlags::uniqueId, uniqueIdProvider }
};
StringArray ids;
#if JUCE_WINDOWS
File f (File::getSpecialLocation (File::windowsSystemDirectory));
#else
File f ("~");
#endif
if (auto num = f.getFileIdentifier())
for (const auto& provider : providers)
{
ids.add (String::toHexString ((int64) num));
}
else
{
for (auto& address : MACAddress::getAllAddresses())
ids.add (address.toString());
if (hasBitValueSet (flags, provider.flag))
provider.func (ids);
}
jassert (! ids.isEmpty()); // Failed to create any IDs!
return ids;
}

View file

@ -157,6 +157,31 @@ public:
*/
static String getUniqueDeviceID();
/** Kinds of identifier that are passed to getMachineIdentifiers(). */
enum class MachineIdFlags
{
macAddresses = 1 << 0, ///< All Mac addresses of the machine.
fileSystemId = 1 << 1, ///< The filesystem id of the user's home directory (or system directory on Windows).
legacyUniqueId = 1 << 2, ///< Only implemented on Windows. A hash of the full smbios table, may be unstable on certain machines.
uniqueId = 1 << 3, ///< The most stable kind of machine identifier. A good default to use.
};
/** Returns a list of strings that can be used to uniquely identify a machine.
To get multiple kinds of identifier at once, you can combine flags using
bitwise-or, e.g. `uniqueId | legacyUniqueId`.
If a particular kind of identifier isn't available, it will be omitted from
the StringArray of results, so passing `uniqueId | legacyUniqueId`
may return 0, 1, or 2 results, depending on the platform and whether any
errors are encountered.
If you've previously generated a machine ID and just want to check it against
all possible identifiers, you can enable all of the flags and check whether
the stored identifier matches any of the results.
*/
static StringArray getMachineIdentifiers (MachineIdFlags flags);
//==============================================================================
// CPU and memory information..
@ -254,4 +279,6 @@ private:
JUCE_DECLARE_NON_COPYABLE (SystemStats)
};
JUCE_DECLARE_SCOPED_ENUM_BITWISE_OPERATORS (SystemStats::MachineIdFlags)
} // namespace juce

View file

@ -324,7 +324,11 @@ JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996)
StringArray OnlineUnlockStatus::MachineIDUtilities::getLocalMachineIDs()
{
auto identifiers = SystemStats::getDeviceIdentifiers();
auto flags = SystemStats::MachineIdFlags::macAddresses
| SystemStats::MachineIdFlags::fileSystemId
| SystemStats::MachineIdFlags::legacyUniqueId
| SystemStats::MachineIdFlags::uniqueId;
auto identifiers = SystemStats::getMachineIdentifiers (flags);
for (auto& identifier : identifiers)
identifier = getEncodedIDString (identifier);