Skip to content

Commit c44d226

Browse files
authored
Merge pull request #124 from AArnott/fix116b
Generate public key fields in ThisAssembly on Linux/Mac
2 parents b9a112a + 3222ec3 commit c44d226

File tree

6 files changed

+363
-53
lines changed

6 files changed

+363
-53
lines changed

src/Nerdbank.GitVersioning.Tasks/AssemblyVersionInfo.cs

Lines changed: 93 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,7 @@
1111
using System.Text;
1212
using Microsoft.Build.Framework;
1313
using Microsoft.Build.Utilities;
14-
#if NET45
15-
using PInvoke;
16-
#endif
14+
1715
public class AssemblyVersionInfo : Task
1816
{
1917
#if NET45
@@ -121,7 +119,7 @@ private CodeTypeDeclaration CreateThisAssemblyClass()
121119

122120
// Determine information about the public key used in the assembly name.
123121
string publicKey, publicKeyToken;
124-
this.ReadKeyInfo(out publicKey, out publicKeyToken);
122+
bool hasKeyInfo = this.TryReadKeyInfo(out publicKey, out publicKeyToken);
125123

126124
// Define the constants.
127125
thisAssembly.Members.AddRange(CreateFields(new Dictionary<string, string>
@@ -130,58 +128,25 @@ private CodeTypeDeclaration CreateThisAssemblyClass()
130128
{ "AssemblyFileVersion", this.AssemblyFileVersion },
131129
{ "AssemblyInformationalVersion", this.AssemblyInformationalVersion },
132130
{ "AssemblyName", this.AssemblyName },
133-
{ "PublicKey", publicKey },
134-
{ "PublicKeyToken", publicKeyToken },
135131
{ "AssemblyTitle", this.AssemblyTitle },
136132
{ "AssemblyProduct", this.AssemblyProduct },
137133
{ "AssemblyCopyright", this.AssemblyCopyright },
138134
{ "AssemblyCompany", this.AssemblyCompany },
139135
{ "AssemblyConfiguration", this.AssemblyConfiguration }
140136
}).ToArray());
141-
142-
// These properties should be defined even if they are empty.
143-
thisAssembly.Members.Add(CreateField("RootNamespace", this.RootNamespace));
144-
145-
return thisAssembly;
146-
}
147-
148-
private void ReadKeyInfo(out string publicKey, out string publicKeyToken)
149-
{
150-
byte[] publicKeyBytes = null;
151-
if (!string.IsNullOrEmpty(this.AssemblyOriginatorKeyFile) && File.Exists(this.AssemblyOriginatorKeyFile))
137+
if (hasKeyInfo)
152138
{
153-
if (Path.GetExtension(this.AssemblyOriginatorKeyFile).Equals(".snk", StringComparison.OrdinalIgnoreCase))
139+
thisAssembly.Members.AddRange(CreateFields(new Dictionary<string, string>
154140
{
155-
byte[] keyBytes = File.ReadAllBytes(this.AssemblyOriginatorKeyFile);
156-
bool publicKeyOnly = keyBytes[0] != 0x07;
157-
publicKeyBytes = publicKeyOnly ? keyBytes : MSCorEE.StrongNameGetPublicKey(keyBytes);
158-
}
159-
}
160-
else if (!string.IsNullOrEmpty(this.AssemblyKeyContainerName))
161-
{
162-
publicKeyBytes = MSCorEE.StrongNameGetPublicKey(this.AssemblyKeyContainerName);
163-
}
164-
165-
if (publicKeyBytes != null && publicKeyBytes.Length > 0) // If .NET 2.0 isn't installed, we get byte[0] back.
166-
{
167-
publicKey = ToHex(publicKeyBytes);
168-
publicKeyToken = ToHex(MSCorEE.StrongNameTokenFromPublicKey(publicKeyBytes));
141+
{ "PublicKey", publicKey },
142+
{ "PublicKeyToken", publicKeyToken },
143+
}).ToArray());
169144
}
170-
else
171-
{
172-
if (publicKeyBytes != null)
173-
{
174-
this.Log.LogWarning("Unable to emit public key fields in ThisAssembly class because .NET 2.0 isn't installed.");
175-
}
176145

177-
publicKey = null;
178-
publicKeyToken = null;
179-
}
180-
}
146+
// These properties should be defined even if they are empty.
147+
thisAssembly.Members.Add(CreateField("RootNamespace", this.RootNamespace));
181148

182-
private static string ToHex(byte[] data)
183-
{
184-
return BitConverter.ToString(data).Replace("-", "").ToLowerInvariant();
149+
return thisAssembly;
185150
}
186151

187152
private IEnumerable<CodeAttributeDeclaration> CreateAssemblyAttributes()
@@ -276,9 +241,9 @@ private void CreateThisAssemblyClass()
276241
{
277242
this.generator.StartThisAssemblyClass();
278243

279-
////// Determine information about the public key used in the assembly name.
280-
////string publicKey, publicKeyToken;
281-
////this.ReadKeyInfo(out publicKey, out publicKeyToken);
244+
// Determine information about the public key used in the assembly name.
245+
string publicKey, publicKeyToken;
246+
bool hasKeyInfo = this.TryReadKeyInfo(out publicKey, out publicKeyToken);
282247

283248
// Define the constants.
284249
var fields = new Dictionary<string, string>
@@ -287,14 +252,18 @@ private void CreateThisAssemblyClass()
287252
{ "AssemblyFileVersion", this.AssemblyFileVersion },
288253
{ "AssemblyInformationalVersion", this.AssemblyInformationalVersion },
289254
{ "AssemblyName", this.AssemblyName },
290-
////{ "PublicKey", publicKey },
291-
////{ "PublicKeyToken", publicKeyToken },
292255
{ "AssemblyTitle", this.AssemblyTitle },
293256
{ "AssemblyProduct", this.AssemblyProduct },
294257
{ "AssemblyCopyright", this.AssemblyCopyright },
295258
{ "AssemblyCompany", this.AssemblyCompany },
296259
{ "AssemblyConfiguration", this.AssemblyConfiguration }
297260
};
261+
if (hasKeyInfo)
262+
{
263+
fields.Add("PublicKey", publicKey);
264+
fields.Add("PublicKeyToken", publicKeyToken);
265+
}
266+
298267
foreach (var pair in fields)
299268
{
300269
if (!string.IsNullOrEmpty(pair.Value))
@@ -389,5 +358,79 @@ internal override void EndThisAssemblyClass()
389358
}
390359
}
391360
#endif
361+
362+
private static string ToHex(byte[] data)
363+
{
364+
return BitConverter.ToString(data).Replace("-", "").ToLowerInvariant();
365+
}
366+
367+
/// <summary>
368+
/// Gets the public key from a key container.
369+
/// </summary>
370+
/// <param name="containerName">The name of the container.</param>
371+
/// <returns>The public key.</returns>
372+
private static byte[] GetPublicKeyFromKeyContainer(string containerName)
373+
{
374+
throw new NotImplementedException();
375+
}
376+
377+
private static byte[] GetPublicKeyFromKeyPair(byte[] keyPair)
378+
{
379+
byte[] publicKey;
380+
if (CryptoBlobParser.TryGetPublicKeyFromPrivateKeyBlob(keyPair, out publicKey))
381+
{
382+
return publicKey;
383+
}
384+
else
385+
{
386+
throw new ArgumentException("Invalid keypair");
387+
}
388+
}
389+
390+
private bool TryReadKeyInfo(out string publicKey, out string publicKeyToken)
391+
{
392+
try
393+
{
394+
byte[] publicKeyBytes = null;
395+
if (!string.IsNullOrEmpty(this.AssemblyOriginatorKeyFile) && File.Exists(this.AssemblyOriginatorKeyFile))
396+
{
397+
if (Path.GetExtension(this.AssemblyOriginatorKeyFile).Equals(".snk", StringComparison.OrdinalIgnoreCase))
398+
{
399+
byte[] keyBytes = File.ReadAllBytes(this.AssemblyOriginatorKeyFile);
400+
bool publicKeyOnly = keyBytes[0] != 0x07;
401+
publicKeyBytes = publicKeyOnly ? keyBytes : GetPublicKeyFromKeyPair(keyBytes);
402+
}
403+
}
404+
else if (!string.IsNullOrEmpty(this.AssemblyKeyContainerName))
405+
{
406+
publicKeyBytes = GetPublicKeyFromKeyContainer(this.AssemblyKeyContainerName);
407+
}
408+
409+
if (publicKeyBytes != null && publicKeyBytes.Length > 0) // If .NET 2.0 isn't installed, we get byte[0] back.
410+
{
411+
publicKey = ToHex(publicKeyBytes);
412+
publicKeyToken = ToHex(CryptoBlobParser.GetStrongNameTokenFromPublicKey(publicKeyBytes));
413+
}
414+
else
415+
{
416+
if (publicKeyBytes != null)
417+
{
418+
this.Log.LogWarning("Unable to emit public key fields in ThisAssembly class because .NET 2.0 isn't installed.");
419+
}
420+
421+
publicKey = null;
422+
publicKeyToken = null;
423+
return false;
424+
}
425+
426+
return true;
427+
}
428+
catch (NotImplementedException)
429+
{
430+
publicKey = null;
431+
publicKeyToken = null;
432+
return false;
433+
}
434+
}
392435
}
393436
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
namespace Nerdbank.GitVersioning.Tasks
2+
{
3+
using System.Runtime.InteropServices;
4+
5+
/// <contents>
6+
/// The <see cref="BlobHeader"/> struct.
7+
/// </contents>
8+
partial class CryptoBlobParser
9+
{
10+
[StructLayout(LayoutKind.Sequential)]
11+
private struct BlobHeader
12+
{
13+
/// <summary>
14+
/// Blob type.
15+
/// </summary>
16+
public byte Type;
17+
18+
/// <summary>
19+
/// Blob format version
20+
/// </summary>
21+
public byte Version;
22+
23+
/// <summary>
24+
/// Must be 0
25+
/// </summary>
26+
public ushort Reserved;
27+
28+
/// <summary>
29+
/// Algorithm ID. Must be one of ALG_ID specified in wincrypto.h
30+
/// </summary>
31+
public uint AlgId;
32+
}
33+
}
34+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
namespace Nerdbank.GitVersioning.Tasks
2+
{
3+
using System.Runtime.InteropServices;
4+
5+
partial class CryptoBlobParser
6+
{
7+
/// <summary>
8+
/// RSAPUBKEY struct from wincrypt.h
9+
/// </summary>
10+
[StructLayout(LayoutKind.Sequential)]
11+
private struct RsaPubKey
12+
{
13+
/// <summary>
14+
/// Indicates RSA1 or RSA2
15+
/// </summary>
16+
public uint Magic;
17+
18+
/// <summary>
19+
/// Number of bits in the modulus. Must be multiple of 8.
20+
/// </summary>
21+
public uint BitLen;
22+
23+
/// <summary>
24+
/// The public exponent
25+
/// </summary>
26+
public uint PubExp;
27+
}
28+
}
29+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
namespace Nerdbank.GitVersioning.Tasks
2+
{
3+
using System.Runtime.InteropServices;
4+
5+
/// <contents>
6+
/// The <see cref="SnPublicKeyBlob"/> struct.
7+
/// </contents>
8+
partial class CryptoBlobParser
9+
{
10+
/// <summary>
11+
/// The strong name public key blob binary format.
12+
/// </summary>
13+
/// <see cref="https://github.com/dotnet/coreclr/blob/32f0f9721afb584b4a14d69135bea7ddc129f755/src/strongname/inc/strongname.h#L29"/>
14+
[StructLayout(LayoutKind.Sequential, Pack = 1)]
15+
private unsafe struct SnPublicKeyBlob
16+
{
17+
/// <summary>
18+
/// Signature algorithm ID
19+
/// </summary>
20+
public uint SigAlgId;
21+
22+
/// <summary>
23+
/// Hash algorithm ID
24+
/// </summary>
25+
public uint HashAlgId;
26+
27+
/// <summary>
28+
/// Size of public key data in bytes, not including the header
29+
/// </summary>
30+
public uint PublicKeySize;
31+
32+
/// <summary>
33+
/// PublicKeySize bytes of public key data
34+
/// </summary>
35+
/// <remarks>
36+
/// Note: PublicKey is variable sized
37+
/// </remarks>
38+
public fixed byte PublicKey[1];
39+
}
40+
}
41+
}

0 commit comments

Comments
 (0)