forked from KirillOsenkov/MetadataTools
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCryptoBlobParser.cs
More file actions
340 lines (287 loc) · 12.4 KB
/
CryptoBlobParser.cs
File metadata and controls
340 lines (287 loc) · 12.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
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
// Heavily inspired by
// https://github.com/dotnet/roslyn/blob/1ead9e9c4dab6dc5092c1690b560dd2f113a236c/src/Compilers/Core/Portable/StrongName/CryptoBlobParser.cs
using System;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
namespace PEFile
{
public static class CryptoBlobParser
{
private enum AlgorithmClass
{
Signature = 1,
Hash = 4,
}
private enum AlgorithmSubId
{
Sha1Hash = 4,
MacHash = 5,
RipeMdHash = 6,
RipeMd160Hash = 7,
Ssl3ShaMD5Hash = 8,
HmacHash = 9,
Tls1PrfHash = 10,
HashReplacOwfHash = 11,
Sha256Hash = 12,
Sha384Hash = 13,
Sha512Hash = 14,
}
private struct AlgorithmId
{
// From wincrypt.h
private const int AlgorithmClassOffset = 13;
private const int AlgorithmClassMask = 0x7;
private const int AlgorithmSubIdOffset = 0;
private const int AlgorithmSubIdMask = 0x1ff;
private readonly uint _flags;
public const int RsaSign = 0x00002400;
public const int Sha = 0x00008004;
public bool IsSet
{
get { return _flags != 0; }
}
public AlgorithmClass Class
{
get { return (AlgorithmClass)((_flags >> AlgorithmClassOffset) & AlgorithmClassMask); }
}
public AlgorithmSubId SubId
{
get { return (AlgorithmSubId)((_flags >> AlgorithmSubIdOffset) & AlgorithmSubIdMask); }
}
public AlgorithmId(uint flags)
{
_flags = flags;
}
}
// From ECMAKey.h
private static readonly byte[] s_ecmaKey = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0 };
private const int SnPublicKeyBlobSize = 13;
// From wincrypt.h
private const byte PublicKeyBlobId = 0x06;
private const byte PrivateKeyBlobId = 0x07;
// internal for testing
internal const int s_publicKeyHeaderSize = SnPublicKeyBlobSize - 1;
// From StrongNameInternal.cpp
// Checks to see if a public key is a valid instance of a PublicKeyBlob as
// defined in StongName.h
internal static bool IsValidPublicKey(byte[] blob)
{
// The number of public key bytes must be at least large enough for the header and one byte of data.
if (blob == null || blob.Length < s_publicKeyHeaderSize + 1)
{
return false;
}
BinaryReader blobReader = new BinaryReader(new MemoryStream(blob));
// Signature algorithm ID
var sigAlgId = blobReader.ReadUInt32();
// Hash algorithm ID
var hashAlgId = blobReader.ReadUInt32();
// Size of public key data in bytes, not including the header
var publicKeySize = blobReader.ReadUInt32();
// publicKeySize bytes of public key data
var publicKey = blobReader.ReadByte();
// The number of public key bytes must be the same as the size of the header plus the size of the public key data.
if (blob.Length != s_publicKeyHeaderSize + publicKeySize)
{
return false;
}
// Check for the ECMA key, which does not obey the invariants checked below.
if (Enumerable.SequenceEqual(blob, s_ecmaKey))
{
return true;
}
// The public key must be in the wincrypto PUBLICKEYBLOB format
if (publicKey != PublicKeyBlobId)
{
return false;
}
var signatureAlgorithmId = new AlgorithmId(sigAlgId);
if (signatureAlgorithmId.IsSet && signatureAlgorithmId.Class != AlgorithmClass.Signature)
{
return false;
}
var hashAlgorithmId = new AlgorithmId(hashAlgId);
if (hashAlgorithmId.IsSet && (hashAlgorithmId.Class != AlgorithmClass.Hash || hashAlgorithmId.SubId < AlgorithmSubId.Sha1Hash))
{
return false;
}
return true;
}
private const int BlobHeaderSize = sizeof(byte) + sizeof(byte) + sizeof(ushort) + sizeof(uint);
private const int RsaPubKeySize = sizeof(uint) + sizeof(uint) + sizeof(uint);
private const UInt32 RSA1 = 0x31415352;
private const UInt32 RSA2 = 0x32415352;
// In wincrypt.h both public and private key blobs start with a
// PUBLICKEYSTRUC and RSAPUBKEY and then start the key data
private const int s_offsetToKeyData = BlobHeaderSize + RsaPubKeySize;
private static byte[] CreateSnPublicKeyBlob(
byte type,
byte version,
uint algId,
uint magic,
uint bitLen,
uint pubExp,
byte[] pubKeyData)
{
var stream = new MemoryStream(3 * sizeof(uint) + s_offsetToKeyData + pubKeyData.Length);
var w = new StreamWriter(stream);
w.Write((uint)AlgorithmId.RsaSign);
w.Write((uint)AlgorithmId.Sha);
w.Write((uint)(s_offsetToKeyData + pubKeyData.Length));
stream.WriteByte(type);
stream.WriteByte(version);
stream.WriteByte((byte)0 /* 16 bits of reserved space in the spec */);
stream.WriteByte((byte)0 /* 16 bits of reserved space in the spec */);
w.Write((uint)algId);
w.Write((uint)magic);
w.Write((uint)bitLen);
// re-add padding for exponent
w.Write((uint)pubExp);
stream.Write(pubKeyData, 0, pubKeyData.Length);
return stream.ToArray();
}
/// <summary>
/// Try to retrieve the public key from a crypto blob.
/// </summary>
/// <remarks>
/// Can be either a PUBLICKEYBLOB or PRIVATEKEYBLOB. The BLOB must be unencrypted.
/// </remarks>
public static bool TryParseKey(byte[] blob, out byte[] snKey, out RSAParameters? privateKey)
{
privateKey = null;
snKey = null;
var asArray = blob;
if (IsValidPublicKey(blob))
{
snKey = blob;
return true;
}
if (blob.Length < BlobHeaderSize + RsaPubKeySize)
{
return false;
}
try
{
using (MemoryStream memStream = new MemoryStream(asArray))
using (BinaryReader br = new BinaryReader(new MemoryStream(asArray)))
{
byte bType = br.ReadByte(); // BLOBHEADER.bType: Expected to be 0x6 (PUBLICKEYBLOB) or 0x7 (PRIVATEKEYBLOB), though there's no check for backward compat reasons.
byte bVersion = br.ReadByte(); // BLOBHEADER.bVersion: Expected to be 0x2, though there's no check for backward compat reasons.
br.ReadUInt16(); // BLOBHEADER.wReserved
uint algId = br.ReadUInt32(); // BLOBHEADER.aiKeyAlg
uint magic = br.ReadUInt32(); // RSAPubKey.magic: Expected to be 0x31415352 ('RSA1') or 0x32415352 ('RSA2')
var bitLen = br.ReadUInt32(); // Bit Length for Modulus
var pubExp = br.ReadUInt32(); // Exponent
var modulusLength = (int)(bitLen / 8);
if (blob.Length - s_offsetToKeyData < modulusLength)
{
return false;
}
var modulus = br.ReadBytes(modulusLength);
if (!(bType == PrivateKeyBlobId && magic == RSA2) && !(bType == PublicKeyBlobId && magic == RSA1))
{
return false;
}
if (bType == PrivateKeyBlobId)
{
privateKey = ToRSAParameters(asArray, true);
// For snKey, rewrite some of the the parameters
algId = AlgorithmId.RsaSign;
magic = RSA1;
}
snKey = CreateSnPublicKeyBlob(PublicKeyBlobId, bVersion, algId, RSA1, bitLen, pubExp, modulus);
return true;
}
}
catch (Exception)
{
return false;
}
}
/// <summary>
/// Helper for RsaCryptoServiceProvider.ExportParameters()
/// Copied from https://github.com/dotnet/corefx/blob/5fe5f9aae7b2987adc7082f90712b265bee5eefc/src/System.Security.Cryptography.Csp/src/System/Security/Cryptography/CapiHelper.Shared.cs
/// </summary>
internal static RSAParameters ToRSAParameters(this byte[] cspBlob, bool includePrivateParameters)
{
BinaryReader br = new BinaryReader(new MemoryStream(cspBlob));
byte bType = br.ReadByte(); // BLOBHEADER.bType: Expected to be 0x6 (PUBLICKEYBLOB) or 0x7 (PRIVATEKEYBLOB), though there's no check for backward compat reasons.
byte bVersion = br.ReadByte(); // BLOBHEADER.bVersion: Expected to be 0x2, though there's no check for backward compat reasons.
br.ReadUInt16(); // BLOBHEADER.wReserved
int algId = br.ReadInt32(); // BLOBHEADER.aiKeyAlg
int magic = br.ReadInt32(); // RSAPubKey.magic: Expected to be 0x31415352 ('RSA1') or 0x32415352 ('RSA2')
int bitLen = br.ReadInt32(); // RSAPubKey.bitLen
int modulusLength = bitLen / 8;
int halfModulusLength = (modulusLength + 1) / 2;
uint expAsDword = br.ReadUInt32();
RSAParameters rsaParameters = new RSAParameters();
rsaParameters.Exponent = ExponentAsBytes(expAsDword);
rsaParameters.Modulus = br.ReadReversed(modulusLength);
if (includePrivateParameters)
{
rsaParameters.P = br.ReadReversed(halfModulusLength);
rsaParameters.Q = br.ReadReversed(halfModulusLength);
rsaParameters.DP = br.ReadReversed(halfModulusLength);
rsaParameters.DQ = br.ReadReversed(halfModulusLength);
rsaParameters.InverseQ = br.ReadReversed(halfModulusLength);
rsaParameters.D = br.ReadReversed(modulusLength);
}
return rsaParameters;
}
/// <summary>
/// Helper for converting a UInt32 exponent to bytes.
/// Copied from https://github.com/dotnet/corefx/blob/5fe5f9aae7b2987adc7082f90712b265bee5eefc/src/System.Security.Cryptography.Csp/src/System/Security/Cryptography/CapiHelper.Shared.cs
/// </summary>
private static byte[] ExponentAsBytes(uint exponent)
{
if (exponent <= 0xFF)
{
return new[] { (byte)exponent };
}
else if (exponent <= 0xFFFF)
{
unchecked
{
return new[]
{
(byte)(exponent >> 8),
(byte)(exponent)
};
}
}
else if (exponent <= 0xFFFFFF)
{
unchecked
{
return new[]
{
(byte)(exponent >> 16),
(byte)(exponent >> 8),
(byte)(exponent)
};
}
}
else
{
return new[]
{
(byte)(exponent >> 24),
(byte)(exponent >> 16),
(byte)(exponent >> 8),
(byte)(exponent)
};
}
}
/// <summary>
/// Read in a byte array in reverse order.
/// Copied from https://github.com/dotnet/corefx/blob/5fe5f9aae7b2987adc7082f90712b265bee5eefc/src/System.Security.Cryptography.Csp/src/System/Security/Cryptography/CapiHelper.Shared.cs
/// </summary>
private static byte[] ReadReversed(this BinaryReader br, int count)
{
byte[] data = br.ReadBytes(count);
Array.Reverse(data);
return data;
}
}
}