Skip to content
This repository has been archived by the owner on Dec 18, 2017. It is now read-only.

Commit

Permalink
Enabling signing assemblies using public key from snk on Mono and Cor…
Browse files Browse the repository at this point in the history
…eClr

If the project.json file contains the `keyFile` on CoreClr and Mono we
will extract the public key from the snk file and will OSS sign the
assembly with the extracted key. (Fixes #2558). We will do the same on
desktop CLR if the `keyFile` option is accompanied by `"useOssSigning": true`

As a result of this change the `keyFile` and the `useOssSigning` are
no longer mutually exclusive and we will no show cyptic Roslyn
compilation errors we would have shown before. (Fixes #2452).
  • Loading branch information
moozzyk committed Oct 20, 2015
1 parent 1eab366 commit 72b0f32
Show file tree
Hide file tree
Showing 14 changed files with 358 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Runtime.Versioning;
using Microsoft.CodeAnalysis;
Expand Down Expand Up @@ -72,21 +73,56 @@ private static CSharpCompilationOptions GetCompilationOptions(ICompilerOptions c
bool allowUnsafe = compilerOptions.AllowUnsafe ?? false;
bool optimize = compilerOptions.Optimize ?? false;
bool warningsAsErrors = compilerOptions.WarningsAsErrors ?? false;
bool strongName = compilerOptions.StrongName ?? false;

Platform platform;
if (!Enum.TryParse(value: platformValue, ignoreCase: true, result: out platform))
{
platform = Platform.AnyCpu;
}

return options.WithAllowUnsafe(allowUnsafe)
.WithPlatform(platform)
.WithGeneralDiagnosticOption(warningsAsErrors ? ReportDiagnostic.Error : ReportDiagnostic.Default)
.WithOptimizationLevel(optimize ? OptimizationLevel.Release : OptimizationLevel.Debug)
.WithCryptoKeyFile(compilerOptions.KeyFile)
.WithDelaySign(compilerOptions.DelaySign)
.WithCryptoPublicKey(strongName ? StrongNameKey : ImmutableArray<byte>.Empty);
options = options
.WithAllowUnsafe(allowUnsafe)
.WithPlatform(platform)
.WithGeneralDiagnosticOption(warningsAsErrors ? ReportDiagnostic.Error : ReportDiagnostic.Default)
.WithOptimizationLevel(optimize ? OptimizationLevel.Release : OptimizationLevel.Debug);

return AddSigningOptions(options, compilerOptions);
}

private static CSharpCompilationOptions AddSigningOptions(CSharpCompilationOptions options, ICompilerOptions compilerOptions)
{
var useOssSigning = compilerOptions.UseOssSigning == true;

var keyFile =
Environment.GetEnvironmentVariable(EnvironmentNames.BuildKeyFile) ??
compilerOptions.KeyFile;

if (!string.IsNullOrEmpty(keyFile))
{
#if DNXCORE50
return options.WithCryptoPublicKey(
SnkUtils.ExtractPublicKey(File.ReadAllBytes(keyFile)));
#else
if (RuntimeEnvironmentHelper.IsMono || useOssSigning)
{
return options.WithCryptoPublicKey(
SnkUtils.ExtractPublicKey(File.ReadAllBytes(keyFile)));
}

options = options.WithCryptoKeyFile(keyFile);

var delaySignString = Environment.GetEnvironmentVariable(EnvironmentNames.BuildDelaySign);
var delaySign =
delaySignString == null
? compilerOptions.DelaySign
: string.Equals(delaySignString, "true", StringComparison.OrdinalIgnoreCase) ||
string.Equals(delaySignString, "1", StringComparison.Ordinal);

return options.WithDelaySign(delaySign);
#endif
}

return useOssSigning ? options.WithCryptoPublicKey(StrongNameKey) : options;
}

private static bool IsDesktop(FrameworkName frameworkName)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

using System.Reflection;
using System.Resources;
using System.Runtime.CompilerServices;

[assembly: AssemblyMetadata("Serviceable", "True")]
[assembly: NeutralResourcesLanguage("en-US")]
[assembly: InternalsVisibleTo("Microsoft.Dnx.Compilation.CSharp.Tests")]
87 changes: 87 additions & 0 deletions src/Microsoft.Dnx.Compilation.CSharp.Common/SnkUtils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Immutable;
using System.IO;

namespace Microsoft.Dnx.Compilation.CSharp
{
internal static class SnkUtils
{
const byte PUBLICKEYBLOB = 0x06;
const byte PRIVATEKEYBLOB = 0x07;

private const uint CALG_RSA_SIGN = 0x00002400;
private const uint CALG_SHA = 0x00008004;

private const uint RSA1 = 0x31415352; //"RSA1" publickeyblob
private const uint RSA2 = 0x32415352; //"RSA2" privatekeyblob

private const int VersionOffset = 1;
private const int ModulusLengthOffset = 12;
private const int ExponentOffset = 16;
private const int MagicPrivateKeyOffset = 8;
private const int MagicPublicKeyOffset = 20;

public static ImmutableArray<byte> ExtractPublicKey(byte[] snk)
{
ValidateBlob(snk);

if (snk[0] != PRIVATEKEYBLOB)
{
return ImmutableArray.Create(snk);
}

var version = snk[VersionOffset];
int modulusBitLength = ReadInt32(snk, ModulusLengthOffset);
uint exponent = (uint)ReadInt32(snk, ExponentOffset);
var modulus = new byte[modulusBitLength >> 3];

Array.Copy(snk, 20, modulus, 0, modulus.Length);

return CreatePublicKey(version, exponent, modulus);
}

private static void ValidateBlob(byte[] snk)
{
// 160 - the size of public key
if (snk.Length >= 160)
{
if (snk[0] == PRIVATEKEYBLOB && ReadInt32(snk, MagicPrivateKeyOffset) == RSA2 || // valid private key
snk[12] == PUBLICKEYBLOB && ReadInt32(snk, MagicPublicKeyOffset) == RSA1) // valid public key
{
return;
}
}

throw new InvalidOperationException("Invalid key file.");
}

private static int ReadInt32(byte[] array, int index)
{
return array[index] | array[index + 1] << 8 | array[index + 2] << 16 | array[index + 3] << 24;
}

private static ImmutableArray<byte> CreatePublicKey(byte version, uint exponent, byte[] modulus)
{
using (var ms = new MemoryStream(160))
using (var binaryWriter = new BinaryWriter(ms))
{
binaryWriter.Write(CALG_RSA_SIGN);
binaryWriter.Write(CALG_SHA);
// total size of the rest of the blob (20 - size of RSAPUBKEY)
binaryWriter.Write(modulus.Length + 20);
binaryWriter.Write(PUBLICKEYBLOB);
binaryWriter.Write(version);
binaryWriter.Write((ushort)0x00000000); // reserved
binaryWriter.Write(CALG_RSA_SIGN);
binaryWriter.Write(RSA1);
binaryWriter.Write(modulus.Length << 3);
binaryWriter.Write(exponent);
binaryWriter.Write(modulus);
return ImmutableArray.Create(ms.ToArray());
}
}
}
}
53 changes: 21 additions & 32 deletions src/Microsoft.Dnx.Compilation.CSharp/RoslynCompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,8 @@ public class RoslynCompiler
incomingReferences,
resourcesResolver);

// Apply strong-name settings
ApplyStrongNameSettings(compilationContext);
ValidateSigningOptions(compilationContext);
AddStrongNameProvider(compilationContext);

if (isMainAspect && projectContext.Files.PreprocessSourceFiles.Any())
{
Expand Down Expand Up @@ -179,43 +179,32 @@ public class RoslynCompiler
return compilationContext;
}

private void ApplyStrongNameSettings(CompilationContext compilationContext)
private void ValidateSigningOptions(CompilationContext compilationContext)
{
var keyFile =
Environment.GetEnvironmentVariable(EnvironmentNames.BuildKeyFile) ??
compilationContext.Compilation.Options.CryptoKeyFile;

if (!string.IsNullOrEmpty(keyFile) && !RuntimeEnvironmentHelper.IsMono)
var compilerOptions = compilationContext.Project.CompilerOptions;
if (compilerOptions.UseOssSigning == false)
{
#if DNX451
var delaySignString = Environment.GetEnvironmentVariable(EnvironmentNames.BuildDelaySign);
var delaySign =
delaySignString == null
? compilationContext.Compilation.Options.DelaySign
: string.Equals(delaySignString, "true", StringComparison.OrdinalIgnoreCase) ||
string.Equals(delaySignString, "1", StringComparison.OrdinalIgnoreCase);

var strongNameProvider = new DesktopStrongNameProvider(
ImmutableArray.Create(compilationContext.Project.ProjectDirectory));
var newOptions = compilationContext.Compilation.Options
.WithStrongNameProvider(strongNameProvider)
.WithCryptoKeyFile(keyFile)
.WithDelaySign(delaySign);
compilationContext.Compilation = compilationContext.Compilation.WithOptions(newOptions);
#else
var diag = Diagnostic.Create(
RoslynDiagnostics.StrongNamingNotSupported,
null);
compilationContext.Diagnostics.Add(diag);
if (!RuntimeEnvironmentHelper.IsMono)
{
return;
}
#endif
compilationContext.Diagnostics.Add(
Diagnostic.Create(RoslynDiagnostics.RealSigningSupportedOnlyOnDesktopClr, null));
}
}

// If both CryptoPublicKey and CryptoKeyFile compilation will fail so we don't need a check
if (compilationContext.Compilation.Options.CryptoPublicKey != null)
private void AddStrongNameProvider(CompilationContext compilationContext)
{
if (!string.IsNullOrEmpty(compilationContext.Compilation.Options.CryptoKeyFile))
{
var options = compilationContext.Compilation.Options
.WithCryptoPublicKey(compilationContext.Compilation.Options.CryptoPublicKey);
compilationContext.Compilation = compilationContext.Compilation.WithOptions(options);
var strongNameProvider =
new DesktopStrongNameProvider(ImmutableArray.Create(compilationContext.Project.ProjectDirectory));

compilationContext.Compilation =
compilationContext.Compilation.WithOptions(
compilationContext.Compilation.Options.WithStrongNameProvider(strongNameProvider));
}
}

Expand Down
6 changes: 3 additions & 3 deletions src/Microsoft.Dnx.Compilation.CSharp/RoslynDiagnostics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ namespace Microsoft.Dnx.Compilation.CSharp
{
internal class RoslynDiagnostics
{
internal static readonly DiagnosticDescriptor StrongNamingNotSupported = new DiagnosticDescriptor(
internal static readonly DiagnosticDescriptor RealSigningSupportedOnlyOnDesktopClr = new DiagnosticDescriptor(
id: "DNX1001",
title: "Strong name generation is not supported on this platform",
messageFormat: "Strong name generation is not supported on CoreCLR. Skipping strong name generation.",
title: "Strong name generation with a private and public key pair is not supported on this platform",
messageFormat: "Strong name generation with a private and public key pair is supported only on desktop CLR. Falling back to OSS signing.",
category: "StrongNaming",
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true);
Expand Down
2 changes: 1 addition & 1 deletion src/Microsoft.Dnx.Runtime.Abstractions/ICompilerOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public interface ICompilerOptions

bool? DelaySign { get; }

bool? StrongName { get; }
bool? UseOssSigning { get; }

bool? EmitEntryPoint { get; }
}
Expand Down
6 changes: 3 additions & 3 deletions src/Microsoft.Dnx.Runtime/Compilation/CompilerOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public class CompilerOptions : ICompilerOptions

public bool? DelaySign { get; set; }

public bool? StrongName { get; set; }
public bool? UseOssSigning { get; set; }

public bool? EmitEntryPoint { get; set; }

Expand Down Expand Up @@ -81,9 +81,9 @@ public static CompilerOptions Combine(params CompilerOptions[] options)
result.DelaySign = option.DelaySign;
}

if (option.StrongName != null)
if (option.UseOssSigning != null)
{
result.StrongName = option.StrongName;
result.UseOssSigning = option.UseOssSigning;
}

if (option.EmitEntryPoint != null)
Expand Down
2 changes: 1 addition & 1 deletion src/Microsoft.Dnx.Runtime/ProjectReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,7 @@ private static CompilerOptions GetCompilationOptions(JsonObject rawObject)
Optimize = rawOptions.ValueAsNullableBoolean("optimize"),
KeyFile = rawOptions.ValueAsString("keyFile"),
DelaySign = rawOptions.ValueAsNullableBoolean("delaySign"),
StrongName = rawOptions.ValueAsNullableBoolean("strongName"),
UseOssSigning = rawOptions.ValueAsNullableBoolean("useOssSigning"),
EmitEntryPoint = rawOptions.ValueAsNullableBoolean("emitEntryPoint")
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
</PropertyGroup>
<PropertyGroup Label="Configuration">
<RootNamespace>Microsoft.Dnx.Compilation.CSharp.Tests</RootNamespace>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
Expand Down

0 comments on commit 72b0f32

Please sign in to comment.