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
47BOOLEAN g_fLoggingInitialized = FALSE;
48DWORD g_dwLoggingFlags = FALSE;
49
50#define SNLOG(args) Log args
51
52void 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
104enum 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.
114struct 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.
200SNAPI_(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.
214SN_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.
236VOID 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.
248SNAPI_(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.
270SNAPI 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 }
407Exit:
408#else
409 DacNotImpl();
410#endif // #ifndef DACCESS_COMPILE
411 END_ENTRYPOINT_VOIDRET;
412
413 return retVal;
414
415}
416