-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMain.cpp
More file actions
252 lines (211 loc) · 10.4 KB
/
Copy pathMain.cpp
File metadata and controls
252 lines (211 loc) · 10.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
/* Program to get a unique identifier for the TPM chip on the computer.
Computes the SHA-256 hash of the public-key part of the TPM endorsement key (EKpub).
The EK is unique for every TPM and can identify it. The EK can't be changed or removed (from https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/tpm-key-attestation). */
#include <windows.h>
#include <bcrypt.h> // basic cryptographic primitives
#include <ncrypt.h> // key storage and retrieval
#include <cassert>
#include <fstream>
#include <vector>
#pragma comment(lib, "Ncrypt.lib")
#pragma comment(lib, "Crypt32.lib")
#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
#define STATUS_UNSUCCESSFUL ((NTSTATUS)0xC0000001L)
/** RSA public key BLOB */
struct RsaPublicBlob {
// RSA public key buffer. Shall contain {BCRYPT_RSAKEY_BLOB, PublicExponent, Modulus} in big-endian encoding.
std::vector<BYTE> buffer;
const BCRYPT_RSAKEY_BLOB* Header() const {
auto* header = (BCRYPT_RSAKEY_BLOB*)buffer.data();
assert(header->Magic == BCRYPT_RSAPUBLIC_MAGIC); // 0x31415352 // RSA1
assert(!header->cbPrime1 && !header->cbPrime2);
assert(buffer.size() == sizeof(BCRYPT_RSAKEY_BLOB) + header->cbPublicExp + header->cbModulus);
return header;
}
/** Return RSA exponent in big-endian format */
std::vector<BYTE> Exponent() const {
const BCRYPT_RSAKEY_BLOB* header = Header();
const BYTE* ptr = buffer.data() + sizeof(BCRYPT_RSAKEY_BLOB);
std::vector<BYTE> exponent(header->cbPublicExp, 0); // big-endian
memcpy(exponent.data(), ptr, header->cbPublicExp);
return exponent;
}
/** Return RSA modulus in big-endian format */
std::vector<BYTE> Modulus() const {
const BCRYPT_RSAKEY_BLOB* header = Header();
const BYTE* ptr = buffer.data() + sizeof(BCRYPT_RSAKEY_BLOB) + header->cbPublicExp;
std::vector<BYTE> modulus(header->cbModulus, 0); // big-endian
memcpy(modulus.data(), ptr, header->cbModulus);
return modulus;
}
/** Compute ASN.1 DER encoding of the public key.
Based on the WritePkcs1PublicKey function in https://github.com/dotnet/runtime/blob/main/src/libraries/Common/src/System/Security/Cryptography/RSAKeyFormatHelper.Pkcs1.cs
Matches the following implementations:
* PowerShell: (Get-TpmEndorsementKeyInfo -Hash "Sha256").PublicKey
* .Net: RSA.Create(parameters).ExportRSAPublicKey() with parameters.Exponent and parameters.Modulus set. */
std::vector<BYTE> PublicKey() const {
// Encoding reference: https://learn.microsoft.com/en-us/windows/win32/seccertenroll/about-der-encoding-of-asn-1-types
std::vector<BYTE> data;
#if 1
auto modulus = Modulus();
uint16_t modLen = (uint16_t)modulus.size(); // typ. 256bytes
if (modulus[0] & 0x80)
modLen += 1; // need to add leading byte to signal positive value
auto exponent = Exponent();
uint8_t expLen = (uint8_t)exponent.size(); // typ. 3bytes
if (exponent[0] & 0x80)
expLen += 1; // need to add leading byte to signal positive value
uint16_t payloadLen = (4 + modLen) + (2 + expLen); // typ. 266bytes
data.push_back(0x30); // SEQUENCE
data.push_back(0x82); // 2 bytes length prefix (MSB set)
data.push_back(payloadLen >> 8); // payload length (big-endian)
data.push_back(payloadLen & 0xFF);
{
// Modulus parameter
data.push_back(0x02); // INTEGER value
data.push_back(0x82); // 2 bytes length prefix (MSB set)
data.push_back(modLen >> 8); // modulus length (big-endian)
data.push_back(modLen & 0xFF);
if (modulus[0] & 0x80)
data.push_back(0x00); // add leading 0x00 to indicate positive value
data.insert(data.end(), modulus.begin(), modulus.end());
}
{
// Exponent parameter
data.push_back(0x02); // INTEGER value
data.push_back(expLen); // exponent length
if (exponent[0] & 0x80)
data.push_back(0x00); // add leading 0x00 to indicate positive value
data.insert(data.end(), exponent.begin(), exponent.end());
}
#else
// work-in-progress alternative impl.
NCRYPT_PROV_HANDLE provHandle = 0;
NCryptOpenStorageProvider(&provHandle, MS_KEY_STORAGE_PROVIDER, 0);
// step 1: Import BCRYPT_RSAKEY_BLOB-encoded key
NCRYPT_KEY_HANDLE nkey = 0;
SECURITY_STATUS status = NCryptImportKey(provHandle, NULL, BCRYPT_RSAPUBLIC_BLOB, NULL, &nkey, data.data(), (DWORD)data.size(), 0);
BCRYPT_ALG_HANDLE algHandle = 0;
std::string keyObj = ""; // optional
NTSTATUS status2 = BCryptImportKey(algHandle, NULL, BCRYPT_RSAPUBLIC_BLOB, NULL, (UCHAR*)keyObj.c_str(), (DWORD)keyObj.length(), data.data(), (DWORD)data.size(), 0);
// step 2: Export ASN.1 DER encoding
// TODO: Implement
NCryptFreeObject(provHandle);
#endif
return data;
}
void PrintHeader() const {
const BCRYPT_RSAKEY_BLOB* header = Header();
printf("Algorithm: ");
for (size_t i = 0; i < sizeof(header->Magic); i++) {
BYTE elm = ((BYTE*)&header->Magic)[i];
printf("%c", elm);
}
printf("\n");
printf("Bit length: %u\n", header->BitLength);
printf("Exponent length: %u\n", header->cbPublicExp);
printf("Modulus length: %u\n", header->cbModulus);
printf("Prime1 length: %u\n", header->cbPrime1);
printf("Prime2 length: %u\n", header->cbPrime2);
}
void SaveToFile(const char* filename) const {
std::ofstream file(filename, std::ofstream::out | std::ofstream::binary);
file.write((char*)&buffer, buffer.size());
}
};
/** Compute SHA-256 hash of the input data. */
static std::vector<BYTE> Sha256Hash(const std::vector<BYTE>& data) {
std::vector<BYTE> hash(32, 0); // SHA-256 output is always 32bytes
// open hash algorithm provider
NTSTATUS status = STATUS_UNSUCCESSFUL;
BCRYPT_ALG_HANDLE hAlg = NULL;
if (!NT_SUCCESS(status = BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_SHA256_ALGORITHM, NULL, 0)))
abort();
// get scratch buffer size
ULONG scratchBufLen = 0, scratchBufLenSize = 0;
if (!NT_SUCCESS(status = BCryptGetProperty(hAlg, BCRYPT_OBJECT_LENGTH, (UCHAR*)&scratchBufLen, sizeof(scratchBufLen), &scratchBufLenSize, 0)))
abort();
assert(scratchBufLenSize == sizeof(scratchBufLen));
// create a hash object
std::vector<BYTE> scratchBuf(scratchBufLen, 0);
BCRYPT_HASH_HANDLE hHash = NULL;
if (!NT_SUCCESS(status = BCryptCreateHash(hAlg, &hHash, scratchBuf.data(), (ULONG)scratchBuf.size(), NULL, 0, 0)))
abort();
// compute hash of data
if (!NT_SUCCESS(status = BCryptHashData(hHash, (UCHAR*)data.data(), (ULONG)data.size(), 0)))
abort();
// get hash value
if (!NT_SUCCESS(status = BCryptFinishHash(hHash, hash.data(), (ULONG)hash.size(), 0)))
abort();
BCryptDestroyHash(hHash);
BCryptCloseAlgorithmProvider(hAlg, 0);
return hash;
}
/** Compute 32bit cyclic redundancy checksum (CRC-32). */
static uint32_t Crc32Checksum(const std::vector<BYTE>& data) {
#if 1
// low-level implementation based on https://en.wikipedia.org/wiki/Computation_of_cyclic_redundancy_checks#CRC-32_example
uint32_t CRCTable[256] = {};
{
// table initialization
uint32_t crc32 = 1;
// CRCTable[0] = 0 already.
for (unsigned int i = 128; i; i >>= 1) {
crc32 = (crc32 >> 1) ^ (crc32 & 1 ? 0xedb88320 : 0);
for (unsigned int j = 0; j < 256; j += 2 * i)
CRCTable[i + j] = crc32 ^ CRCTable[j];
}
}
uint32_t crc32 = 0xFFFFFFFFu;
for (size_t i = 0; i < data.size(); i++) {
crc32 ^= data[i];
crc32 = (crc32 >> 8) ^ CRCTable[crc32 & 0xff];
}
// Finalize the CRC-32 value by inverting all the bits
crc32 ^= 0xFFFFFFFFu;
return crc32;
#else
// call undocumented RtlComputeCrc32 function in Ntdll.dll
// intended as reference to verify correctness of the implementation above
typedef DWORD (WINAPI* RtlComputeCrc32Fn)(DWORD initial, const BYTE* buffer, UINT buflen);
HMODULE ntdll = GetModuleHandleW(L"ntdll.dll");
auto RtlComputeCrc32 = (RtlComputeCrc32Fn)GetProcAddress(ntdll, "RtlComputeCrc32");
return RtlComputeCrc32(0, data.data(), (UINT)data.size());
#endif
}
int main() {
RsaPublicBlob rsaBlob;
{
// connect to TPM chip that is exposed through the "Microsoft Platform Crypto Provider"
NCRYPT_PROV_HANDLE hProv = NULL;
SECURITY_STATUS ret = NCryptOpenStorageProvider(&hProv, MS_PLATFORM_CRYPTO_PROVIDER, 0);
if (ret != ERROR_SUCCESS) {
printf("ERROR: Unable to connect to TPM chip (NCryptOpenStorageProvider error %u).\n", ret);
abort();
}
// Retrieve TPM Endorsement Key public key (EKpub) as RSA public key BLOB.
// This key is unique for every TPM and can identify it. It cannot be changed, except if replacing the TPM chip.
// DOC: https://learn.microsoft.com/en-us/windows/win32/api/bcrypt/ns-bcrypt-bcrypt_rsakey_blob
rsaBlob.buffer.resize(1024, 0);
DWORD ekPub_len = 0;
ret = NCryptGetProperty(hProv, NCRYPT_PCP_RSA_EKPUB_PROPERTY, rsaBlob.buffer.data(), (DWORD)rsaBlob.buffer.size(), &ekPub_len, 0); // "PCP_RSA_EKPUB"
if (ret != ERROR_SUCCESS) {
printf("ERROR: Unable to retrieve TPM Endorsement Key public key (EKpub) (NCryptGetProperty error %u).\n", ret);
abort();
}
rsaBlob.buffer.resize(ekPub_len);
NCryptFreeObject(hProv);
}
// compute hash that matches the PowerShell (Get-TpmEndorsementKeyInfo -Hash "Sha256").PublicKeyHash command
printf("Retrieving Endorsement Key public key (EKpub) from the Trusted Platform Module (TPM).\n");
printf("This key is unique for every TPM and can identify it. It cannot be changed, except if replacing the TPM.\n");
printf("\n");
std::vector<BYTE> EKpub = rsaBlob.PublicKey();
std::vector<BYTE> hash = Sha256Hash(EKpub);
printf("TPM EKpub SHA-256 hash: ");
for (BYTE elm : hash)
printf("%02x", elm);
printf(" (secure identifier)\n");
uint32_t crc32 = Crc32Checksum(EKpub);
printf("TPM EKpub CRC-32 checksum: %x (identifier)\n", crc32);
}