1 | // Licensed to the .NET Foundation under one or more agreements. |
2 | // The .NET Foundation licenses this file to you under the MIT license. |
3 | // See the LICENSE file in the project root for more information. |
4 | |
5 | // =========================================================================== |
6 | // File: StrongName.cpp |
7 | // |
8 | // Wrappers for signing and hashing functions needed to implement strong names |
9 | // =========================================================================== |
10 | |
11 | #include "common.h" |
12 | #include <imagehlp.h> |
13 | |
14 | #include <winwrap.h> |
15 | #include <windows.h> |
16 | #include <wincrypt.h> |
17 | #include <stddef.h> |
18 | #include <stdio.h> |
19 | #include <malloc.h> |
20 | #include <cor.h> |
21 | #include <corimage.h> |
22 | #include <metadata.h> |
23 | #include <daccess.h> |
24 | #include <limits.h> |
25 | #include <ecmakey.h> |
26 | #include <sha1.h> |
27 | |
28 | #include "strongname.h" |
29 | #include "ex.h" |
30 | #include "pedecoder.h" |
31 | #include "strongnameholders.h" |
32 | #include "strongnameinternal.h" |
33 | #include "common.h" |
34 | #include "classnames.h" |
35 | |
36 | // Debug logging. |
37 | #if !defined(_DEBUG) || defined(DACCESS_COMPILE) |
38 | #define SNLOG(args) |
39 | #endif // !_DEBUG || DACCESS_COMPILE |
40 | |
41 | #ifndef DACCESS_COMPILE |
42 | |
43 | // Debug logging. |
44 | #if defined(_DEBUG) |
45 | #include <stdarg.h> |
46 | |
47 | BOOLEAN g_fLoggingInitialized = FALSE; |
48 | DWORD g_dwLoggingFlags = FALSE; |
49 | |
50 | #define SNLOG(args) Log args |
51 | |
52 | void Log(__in_z const WCHAR *wszFormat, ...) |
53 | { |
54 | if (g_fLoggingInitialized && !g_dwLoggingFlags) |
55 | return; |
56 | |
57 | DWORD dwError = GetLastError(); |
58 | |
59 | if (!g_fLoggingInitialized) { |
60 | g_dwLoggingFlags = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_MscorsnLogging); |
61 | g_fLoggingInitialized = TRUE; |
62 | } |
63 | |
64 | if (!g_dwLoggingFlags) { |
65 | SetLastError(dwError); |
66 | return; |
67 | } |
68 | |
69 | va_list pArgs; |
70 | WCHAR wszBuffer[1024]; |
71 | static WCHAR wszPrefix[] = W("SN: " ); |
72 | |
73 | wcscpy_s(wszBuffer, COUNTOF(wszBuffer), wszPrefix); |
74 | |
75 | va_start(pArgs, wszFormat); |
76 | _vsnwprintf_s(&wszBuffer[COUNTOF(wszPrefix) - 1], |
77 | COUNTOF(wszBuffer) - COUNTOF(wszPrefix), |
78 | _TRUNCATE, |
79 | wszFormat, |
80 | pArgs); |
81 | |
82 | wszBuffer[COUNTOF(wszBuffer) - 1] = W('\0'); |
83 | va_end(pArgs); |
84 | |
85 | if (g_dwLoggingFlags & 1) |
86 | wprintf(W("%s" ), wszBuffer); |
87 | if (g_dwLoggingFlags & 2) |
88 | WszOutputDebugString(wszBuffer); |
89 | if (g_dwLoggingFlags & 4) |
90 | { |
91 | MAKE_UTF8PTR_FROMWIDE_NOTHROW(szMessage, wszBuffer); |
92 | if(szMessage != NULL) |
93 | LOG((LF_SECURITY, LL_INFO100, szMessage)); |
94 | } |
95 | |
96 | SetLastError(dwError); |
97 | } |
98 | |
99 | #endif // _DEBUG |
100 | |
101 | // Size in bytes of strong name token. |
102 | #define SN_SIZEOF_TOKEN 8 |
103 | |
104 | enum StrongNameCachedCsp { |
105 | None = -1, |
106 | Sha1CachedCsp = 0, |
107 | Sha2CachedCsp = Sha1CachedCsp + 1, |
108 | CachedCspCount = Sha2CachedCsp + 1 |
109 | }; |
110 | |
111 | // We cache a couple of things on a per thread basis: the last error encountered |
112 | // and (potentially) CSP contexts. The following structure tracks these and is |
113 | // allocated lazily as needed. |
114 | struct SN_THREAD_CTX { |
115 | DWORD m_dwLastError; |
116 | }; |
117 | |
118 | #endif // !DACCESS_COMPILE |
119 | |
120 | // Macro containing common code used at the start of most APIs. |
121 | #define SN_COMMON_PROLOG() do { \ |
122 | HRESULT __hr = InitStrongName(); \ |
123 | if (FAILED(__hr)) { \ |
124 | SetStrongNameErrorInfo(__hr); \ |
125 | retVal = FALSE; \ |
126 | goto Exit; \ |
127 | } \ |
128 | SetStrongNameErrorInfo(S_OK); \ |
129 | } while (0) |
130 | |
131 | // Macro to return an error from a SN entrypoint API |
132 | #define SN_ERROR(__hr) do { \ |
133 | if (FAILED(__hr)) { \ |
134 | SetStrongNameErrorInfo(__hr); \ |
135 | retVal = FALSE; \ |
136 | goto Exit; \ |
137 | } \ |
138 | } while (false) |
139 | |
140 | // Determine the size of a PublicKeyBlob structure given the size of the key |
141 | // portion. |
142 | #define SN_SIZEOF_KEY(_pKeyBlob) (offsetof(PublicKeyBlob, PublicKey) + GET_UNALIGNED_VAL32(&(_pKeyBlob)->cbPublicKey)) |
143 | |
144 | // We allow a special abbreviated form of the Microsoft public key (16 bytes |
145 | // long: 0 for both alg ids, 4 for key length and 4 bytes of 0 for the key |
146 | // itself). This allows us to build references to system libraries that are |
147 | // platform neutral (so a 3rd party can build mscorlib replacements). The |
148 | // special zero PK is just shorthand for the local runtime's real system PK, |
149 | // which is always used to perform the signature verification, so no security |
150 | // hole is opened by this. Therefore we need to store a copy of the real PK (for |
151 | // this platform) here. |
152 | |
153 | // the actual definition of the microsoft key is in separate file to allow custom keys |
154 | #include "thekey.h" |
155 | |
156 | |
157 | #define SN_THE_KEY() ((PublicKeyBlob*)g_rbTheKey) |
158 | #define SN_SIZEOF_THE_KEY() sizeof(g_rbTheKey) |
159 | |
160 | #define SN_THE_KEYTOKEN() ((PublicKeyBlob*)g_rbTheKeyToken) |
161 | |
162 | // Determine if the given public key blob is the neutral key. |
163 | #define SN_IS_NEUTRAL_KEY(_pk) (SN_SIZEOF_KEY((PublicKeyBlob*)(_pk)) == sizeof(g_rbNeutralPublicKey) && \ |
164 | memcmp((_pk), g_rbNeutralPublicKey, sizeof(g_rbNeutralPublicKey)) == 0) |
165 | |
166 | #define SN_IS_THE_KEY(_pk) (SN_SIZEOF_KEY((PublicKeyBlob*)(_pk)) == sizeof(g_rbTheKey) && \ |
167 | memcmp((_pk), g_rbTheKey, sizeof(g_rbTheKey)) == 0) |
168 | |
169 | |
170 | // Silverlight platform key |
171 | #define SN_THE_SILVERLIGHT_PLATFORM_KEYTOKEN() ((PublicKeyBlob*)g_rbTheSilverlightPlatformKeyToken) |
172 | #define SN_IS_THE_SILVERLIGHT_PLATFORM_KEY(_pk) (SN_SIZEOF_KEY((PublicKeyBlob*)(_pk)) == sizeof(g_rbTheSilverlightPlatformKey) && \ |
173 | memcmp((_pk), g_rbTheSilverlightPlatformKey, sizeof(g_rbTheSilverlightPlatformKey)) == 0) |
174 | |
175 | // Silverlight key |
176 | #define SN_IS_THE_SILVERLIGHT_KEY(_pk) (SN_SIZEOF_KEY((PublicKeyBlob*)(_pk)) == sizeof(g_rbTheSilverlightKey) && \ |
177 | memcmp((_pk), g_rbTheSilverlightKey, sizeof(g_rbTheSilverlightKey)) == 0) |
178 | |
179 | #define SN_THE_SILVERLIGHT_KEYTOKEN() ((PublicKeyBlob*)g_rbTheSilverlightKeyToken) |
180 | |
181 | #ifdef FEATURE_WINDOWSPHONE |
182 | // Microsoft.Phone.* key |
183 | #define SN_THE_MICROSOFT_PHONE_KEYTOKEN() ((PublicKeyBlob*)g_rbTheMicrosoftPhoneKeyToken) |
184 | |
185 | #define SN_IS_THE_MICROSOFT_PHONE_KEY(_pk) (SN_SIZEOF_KEY((PublicKeyBlob*)(_pk)) == sizeof(g_rbTheMicrosoftPhoneKey) && \ |
186 | memcmp((_pk), g_rbTheMicrosoftPhoneKey, sizeof(g_rbTheMicrosoftPhoneKey)) == 0) |
187 | |
188 | // Microsoft.Xna.* key |
189 | #define SN_THE_MICROSOFT_XNA_KEYTOKEN() ((PublicKeyBlob*)g_rbTheMicrosoftXNAKeyToken) |
190 | |
191 | #define SN_IS_THE_MICROSOFT_XNA_KEY(_pk) (SN_SIZEOF_KEY((PublicKeyBlob*)(_pk)) == sizeof(g_rbTheMicrosoftXNAKey) && \ |
192 | memcmp((_pk), g_rbTheMicrosoftXNAKey, sizeof(g_rbTheMicrosoftXNAKey)) == 0) |
193 | |
194 | #endif // FEATURE_WINDOWSPHONE |
195 | |
196 | #define InitStrongName() S_OK |
197 | |
198 | |
199 | // Free buffer allocated by routines below. |
200 | SNAPI_(VOID) StrongNameFreeBuffer(BYTE *pbMemory) // [in] address of memory to free |
201 | { |
202 | BEGIN_ENTRYPOINT_VOIDRET; |
203 | |
204 | SNLOG((W("StrongNameFreeBuffer(%08X)\n" ), pbMemory)); |
205 | |
206 | if (pbMemory != (BYTE*)SN_THE_KEY() && pbMemory != g_rbNeutralPublicKey) |
207 | delete [] pbMemory; |
208 | END_ENTRYPOINT_VOIDRET; |
209 | |
210 | } |
211 | |
212 | #ifndef DACCESS_COMPILE |
213 | // Retrieve per-thread context, lazily allocating it if necessary. |
214 | SN_THREAD_CTX *GetThreadContext() |
215 | { |
216 | SN_THREAD_CTX *pThreadCtx = (SN_THREAD_CTX*)ClrFlsGetValue(TlsIdx_StrongName); |
217 | if (pThreadCtx == NULL) { |
218 | pThreadCtx = new (nothrow) SN_THREAD_CTX; |
219 | if (pThreadCtx == NULL) |
220 | return NULL; |
221 | pThreadCtx->m_dwLastError = S_OK; |
222 | |
223 | EX_TRY { |
224 | ClrFlsSetValue(TlsIdx_StrongName, pThreadCtx); |
225 | } |
226 | EX_CATCH { |
227 | delete pThreadCtx; |
228 | pThreadCtx = NULL; |
229 | } |
230 | EX_END_CATCH (SwallowAllExceptions); |
231 | } |
232 | return pThreadCtx; |
233 | } |
234 | |
235 | // Set the per-thread last error code. |
236 | VOID SetStrongNameErrorInfo(DWORD dwStatus) |
237 | { |
238 | SN_THREAD_CTX *pThreadCtx = GetThreadContext(); |
239 | if (pThreadCtx == NULL) |
240 | // We'll return E_OUTOFMEMORY when we attempt to get the error. |
241 | return; |
242 | pThreadCtx->m_dwLastError = dwStatus; |
243 | } |
244 | |
245 | #endif // !DACCESS_COMPILE |
246 | |
247 | // Return last error. |
248 | SNAPI_(DWORD) StrongNameErrorInfo(VOID) |
249 | { |
250 | HRESULT hr = E_FAIL; |
251 | |
252 | BEGIN_ENTRYPOINT_NOTHROW; |
253 | |
254 | #ifndef DACCESS_COMPILE |
255 | SN_THREAD_CTX *pThreadCtx = GetThreadContext(); |
256 | if (pThreadCtx == NULL) |
257 | hr = E_OUTOFMEMORY; |
258 | else |
259 | hr = pThreadCtx->m_dwLastError; |
260 | #else |
261 | hr = E_FAIL; |
262 | #endif // #ifndef DACCESS_COMPILE |
263 | END_ENTRYPOINT_NOTHROW; |
264 | |
265 | return hr; |
266 | } |
267 | |
268 | |
269 | // Create a strong name token from a public key blob. |
270 | SNAPI StrongNameTokenFromPublicKey(BYTE *pbPublicKeyBlob, // [in] public key blob |
271 | ULONG cbPublicKeyBlob, |
272 | BYTE **ppbStrongNameToken, // [out] strong name token |
273 | ULONG *pcbStrongNameToken) |
274 | { |
275 | BOOLEAN retVal = FALSE; |
276 | |
277 | BEGIN_ENTRYPOINT_VOIDRET; |
278 | |
279 | #ifndef DACCESS_COMPILE |
280 | |
281 | SHA1Hash sha1; |
282 | BYTE *pHash = NULL; |
283 | DWORD i; |
284 | PublicKeyBlob *pPublicKey = NULL; |
285 | DWORD dwHashLenMinusTokenSize = 0; |
286 | |
287 | SNLOG((W("StrongNameTokenFromPublicKey(%08X, %08X, %08X, %08X)\n" ), pbPublicKeyBlob, cbPublicKeyBlob, ppbStrongNameToken, pcbStrongNameToken)); |
288 | |
289 | #if STRONGNAME_IN_VM |
290 | FireEtwSecurityCatchCall_V1(GetClrInstanceId()); |
291 | #endif // STRONGNAME_IN_VM |
292 | |
293 | SN_COMMON_PROLOG(); |
294 | |
295 | if (pbPublicKeyBlob == NULL) |
296 | SN_ERROR(E_POINTER); |
297 | if (!StrongNameIsValidPublicKey(pbPublicKeyBlob, cbPublicKeyBlob, false)) |
298 | SN_ERROR(CORSEC_E_INVALID_PUBLICKEY); |
299 | if (ppbStrongNameToken == NULL) |
300 | SN_ERROR(E_POINTER); |
301 | if (pcbStrongNameToken == NULL) |
302 | SN_ERROR(E_POINTER); |
303 | |
304 | // Allocate a buffer for the output token. |
305 | *ppbStrongNameToken = new (nothrow) BYTE[SN_SIZEOF_TOKEN]; |
306 | if (*ppbStrongNameToken == NULL) { |
307 | SetStrongNameErrorInfo(E_OUTOFMEMORY); |
308 | goto Exit; |
309 | } |
310 | *pcbStrongNameToken = SN_SIZEOF_TOKEN; |
311 | |
312 | // We cache a couple of common cases. |
313 | if (SN_IS_NEUTRAL_KEY(pbPublicKeyBlob)) { |
314 | memcpy_s(*ppbStrongNameToken, *pcbStrongNameToken, g_rbNeutralPublicKeyToken, SN_SIZEOF_TOKEN); |
315 | retVal = TRUE; |
316 | goto Exit; |
317 | } |
318 | if (cbPublicKeyBlob == SN_SIZEOF_THE_KEY() && |
319 | memcmp(pbPublicKeyBlob, SN_THE_KEY(), cbPublicKeyBlob) == 0) { |
320 | memcpy_s(*ppbStrongNameToken, *pcbStrongNameToken, SN_THE_KEYTOKEN(), SN_SIZEOF_TOKEN); |
321 | retVal = TRUE; |
322 | goto Exit; |
323 | } |
324 | |
325 | if (SN_IS_THE_SILVERLIGHT_PLATFORM_KEY(pbPublicKeyBlob)) |
326 | { |
327 | memcpy_s(*ppbStrongNameToken, *pcbStrongNameToken, SN_THE_SILVERLIGHT_PLATFORM_KEYTOKEN(), SN_SIZEOF_TOKEN); |
328 | retVal = TRUE; |
329 | goto Exit; |
330 | } |
331 | |
332 | if (SN_IS_THE_SILVERLIGHT_KEY(pbPublicKeyBlob)) |
333 | { |
334 | memcpy_s(*ppbStrongNameToken, *pcbStrongNameToken, SN_THE_SILVERLIGHT_KEYTOKEN(), SN_SIZEOF_TOKEN); |
335 | retVal = TRUE; |
336 | goto Exit; |
337 | } |
338 | |
339 | #ifdef FEATURE_WINDOWSPHONE |
340 | |
341 | if (SN_IS_THE_MICROSOFT_PHONE_KEY(pbPublicKeyBlob)) |
342 | { |
343 | memcpy_s(*ppbStrongNameToken, *pcbStrongNameToken, SN_THE_MICROSOFT_PHONE_KEYTOKEN(), SN_SIZEOF_TOKEN); |
344 | retVal = TRUE; |
345 | goto Exit; |
346 | } |
347 | |
348 | if (SN_IS_THE_MICROSOFT_XNA_KEY(pbPublicKeyBlob)) |
349 | { |
350 | memcpy_s(*ppbStrongNameToken, *pcbStrongNameToken, SN_THE_MICROSOFT_XNA_KEYTOKEN(), SN_SIZEOF_TOKEN); |
351 | retVal = TRUE; |
352 | goto Exit; |
353 | } |
354 | |
355 | #endif //FEATURE_WINDOWSPHONE |
356 | |
357 | // To compute the correct public key token, we need to make sure the public key blob |
358 | // was not padded with extra bytes that CAPI CryptImportKey would've ignored. |
359 | // Without this round trip, we would blindly compute the hash over the padded bytes |
360 | // which could make finding a public key token collision a significantly easier task |
361 | // since an attacker wouldn't need to work hard on generating valid key pairs before hashing. |
362 | if (cbPublicKeyBlob <= sizeof(PublicKeyBlob)) { |
363 | SetLastError(CORSEC_E_INVALID_PUBLICKEY); |
364 | goto Error; |
365 | } |
366 | |
367 | // Check that the blob type is PUBLICKEYBLOB. |
368 | pPublicKey = (PublicKeyBlob*) pbPublicKeyBlob; |
369 | |
370 | if (pPublicKey->PublicKey + GET_UNALIGNED_VAL32(&pPublicKey->cbPublicKey) < pPublicKey->PublicKey) { |
371 | SetLastError(CORSEC_E_INVALID_PUBLICKEY); |
372 | goto Error; |
373 | } |
374 | |
375 | if (cbPublicKeyBlob < SN_SIZEOF_KEY(pPublicKey)) { |
376 | SetLastError(CORSEC_E_INVALID_PUBLICKEY); |
377 | goto Error; |
378 | } |
379 | |
380 | if (*(BYTE*) pPublicKey->PublicKey /* PUBLICKEYSTRUC->bType */ != PUBLICKEYBLOB) { |
381 | SetLastError(CORSEC_E_INVALID_PUBLICKEY); |
382 | goto Error; |
383 | } |
384 | |
385 | // Compute a hash over the public key. |
386 | sha1.AddData(pbPublicKeyBlob, cbPublicKeyBlob); |
387 | pHash = sha1.GetHash(); |
388 | static_assert(SHA1_HASH_SIZE >= SN_SIZEOF_TOKEN, "SN_SIZEOF_TOKEN must be smaller or equal to the SHA1_HASH_SIZE" ); |
389 | dwHashLenMinusTokenSize = SHA1_HASH_SIZE - SN_SIZEOF_TOKEN; |
390 | |
391 | // Take the last few bytes of the hash value for our token. (These are the |
392 | // low order bytes from a network byte order point of view). Reverse the |
393 | // order of these bytes in the output buffer to get host byte order. |
394 | for (i = 0; i < SN_SIZEOF_TOKEN; i++) |
395 | (*ppbStrongNameToken)[SN_SIZEOF_TOKEN - (i + 1)] = pHash[i + dwHashLenMinusTokenSize]; |
396 | |
397 | retVal = TRUE; |
398 | goto Exit; |
399 | |
400 | Error: |
401 | SetStrongNameErrorInfo(HRESULT_FROM_GetLastError()); |
402 | |
403 | if (*ppbStrongNameToken) { |
404 | delete [] *ppbStrongNameToken; |
405 | *ppbStrongNameToken = NULL; |
406 | } |
407 | Exit: |
408 | #else |
409 | DacNotImpl(); |
410 | #endif // #ifndef DACCESS_COMPILE |
411 | END_ENTRYPOINT_VOIDRET; |
412 | |
413 | return retVal; |
414 | |
415 | } |
416 | |