ユーザ用ツール

サイト用ツール


サイドバー

プログレス合同会社

広告

windows:vs:tools:setpublickey

SetPublicKey

C#で作成したMicrosoft Access VBA用のライブラリDLLMicrosoft Access VBAで使用するには、RegAsm.exeでレジストリに登録する必要があります。
その際に、署名がないアセンブリ(DLL)を登録しようとすると警告が表示されます。
登録はされますので、警告を無視すればいいのですが、アセンブリに署名を行って警告が表示されないようします。

署名は、厳密な名前ツール(Strong Name Tool : sn.exe)で作成したRSA公開鍵暗号キーペアファイルを使って行います。

今回はCoreComプロジェクトによる階層型のソリューション構成でライブラリDLLを開発しますので、キーペアファイルだけではなく、公開キーも必要になります。

公開キーはsn.exe -tpで表示できるのですが、320桁の16進文字列が複数行に渡って表示されますので、.csprojにコピー&ペーストするとミスが発生しやすいため、その作業を自動的に行うツールSetPublicKeyを作成します。

.csprojの編集

ソリューション、プロジェクトを作成後、.csprojを編集します。
例では、Product.csprojになります。

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <Version>xx.xx.xx.xx</Version>
    <OutputType>Exe</OutputType>
    <TargetFramework>net10.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <AssemblyName>SetPublicKey</AssemblyName>
    <PackAsTool>true</PackAsTool>
    <PackageId>ProgressLLC.Tools.SetPublicKey</PackageId>
    <ToolCommandName>setpublickey</ToolCommandName>
    <PackageReadmeFile>README.md</PackageReadmeFile>
  </PropertyGroup>
  <ItemGroup>
    <None Include="../README.md" Pack="true" PackagePath="\" />
  </ItemGroup>
</Project>

3行目
ツールのバージョンです。
10行目
ツールをインストールするときの名称です。
自環境に合わせて変更してください
11行目
ツールを実行するときの名称です。
12行目、14行目~16行目
README.mdは適宜作成してください。

Program.csの編集

ツール本体のProgram.csを編集します。

using System.Security.Cryptography;
using System.Xml.Linq;

namespace ProgressLLC.Tools.SetPublicKey
{
  internal class Program
  {
    static void Main()
    {
      // ソリューションのディレクトリとソリューション名を取得します。
      var solutionDir = Directory.GetCurrentDirectory();
      var solutionName = new DirectoryInfo(solutionDir).Name;
      // 厳密な名前ツール(sn.exe)で作成したRSA公開鍵暗号キーペアファイルのフルパス名を取得します。
      var snkPath = Path.Combine(solutionDir, $"{solutionName}.snk");
      if (!File.Exists(snkPath))
      {
        Console.WriteLine($"{solutionName}.snkが存在しません!");
        return;
      }
      Console.WriteLine("公開キーを取り出しています...");
      var snkBytes   = File.ReadAllBytes(snkPath);
      var parameters = ParseSnkToRsaParameters(snkBytes);
      byte[] blob    = CreateStrongNamePublicKeyBlob(parameters);
      string publicKeyHex = BitConverter.ToString(blob)
        .Replace("-", "")
        .ToLowerInvariant();
      if (publicKeyHex.Length != 320)
      {
        Console.WriteLine($"公開キーの長さが異常です: {publicKeyHex.Length}バイト");
        return;
      }
      // Coreプロジェクトのcsprojファイルのフルパス名を取得します。
      var csprojPath = Path.Combine(solutionDir, "Core", "Core.csproj");
      if (!File.Exists(csprojPath))
      {
        Console.WriteLine($"Core.csprojが存在しません!: {csprojPath}");
        return;
      }
      Console.WriteLine("Core.csprojに公開キーを埋め込みます...");
      var doc = XDocument.Load(csprojPath);
      var publicKeyElement = doc.Descendants("PublicKey").FirstOrDefault();
      if (publicKeyElement == null)
      {
        Console.WriteLine("PublicKeyノードが見つかりません!");
        return;
      }
      publicKeyElement.Value = publicKeyHex;
      doc.Save(csprojPath);
      Console.WriteLine("公開キーを埋め込みました。");
    }

    /// <summary>
    /// sn.exe -kで出力した.snk(CAPI PRIVATEKEYBLOB)から
    /// sn.exe -pで取り出した公開鍵用RSAParametersを取得します。
    /// </summary>
    static RSAParameters ParseSnkToRsaParameters(byte[] snkBytes)
    {
      var bitlen = (int)BitConverter.ToUInt32(snkBytes, 12);
      var modulusByteLen = bitlen / 8;
      var exponentLe = new byte[4];
      Array.Copy(snkBytes, 16, exponentLe, 0, 4);
      var exponentValue = BitConverter.ToUInt32(exponentLe, 0);
      var exponentBe    = BitConverter.GetBytes(exponentValue);
      Array.Reverse(exponentBe);
      var exponentTrimmed = exponentBe.SkipWhile(b => b == 0).ToArray();
      if (exponentTrimmed.Length == 0)
        exponentTrimmed = [0];
      var modulusFromBlob = new byte[modulusByteLen];
      Array.Copy(snkBytes, 20, modulusFromBlob, 0, modulusByteLen);
      Array.Reverse(modulusFromBlob);
      return new RSAParameters
      {
        Modulus  = modulusFromBlob,
        Exponent = exponentTrimmed
      };
    }

    /// <summary>
    /// sn.exe -tpで表示される形式(PublicKeyBlob: ヘッダ12バイト+CAPI公開鍵)のblobを生成します。
    /// </summary>
    static byte[] CreateStrongNamePublicKeyBlob(RSAParameters p)
    {
      if (p.Modulus == null)
        throw new ArgumentNullException(nameof(p), $"{nameof(p.Modulus)} is null.");
      if (p.Exponent == null)
        throw new ArgumentNullException(nameof(p), $"{nameof(p.Exponent)} is null.");
      var capiLength = 8 + 12 + p.Modulus.Length; // PUBLICKEYSTRUC + RSAPUBKEY + modulus
      using var ms = new MemoryStream();
      using var bw = new BinaryWriter(ms);
      // PublicKeyBlob ヘッダ(12バイト)= sn.exe -pが出力する先頭
      const uint CALG_RSA_SIGN = 0x00002400u;
      const uint CALG_SHA1     = 0x00008004u;
      bw.Write(CALG_RSA_SIGN);    // SigAlgId
      bw.Write(CALG_SHA1);        // HashAlgId
      bw.Write((uint)capiLength); // cbPublicKey(以下のCAPI部分の長さ)
      // CAPI PUBLICKEYBLOB
      bw.Write((byte)0x06);       // PUBLICKEYBLOB
      bw.Write((byte)0x02);       // Version
      bw.Write((ushort)0x0000);   // Reserved
      bw.Write((uint)0x00002400); // CALG_RSA_SIGN
      bw.Write(0x31415352u);                    // "RSA1"
      bw.Write((uint)(p.Modulus.Length * 8));   // bitlen
      bw.Write(ToUInt32BigEndian(p.Exponent));  // exponent (little-endian)
      var modulusLE = p.Modulus.Reverse().ToArray();
      bw.Write(modulusLE);
      return ms.ToArray();
    }

    /// <summary>
    /// 4バイトのリトルエンディアン配列をビッグエンディアンのUInt32に変換します。
    /// </summary>
    static uint ToUInt32BigEndian(byte[] bytes)
    {
      var padded = new byte[4];
      Array.Copy(bytes, 0, padded, 4 - bytes.Length, bytes.Length);
      Array.Reverse(padded);
      return BitConverter.ToUInt32(padded, 0);
    }
  }
}

4行目
自環境に合わせて変更してください
11行目
ツールはソリューションルートで実行することを想定しています。
12行目
ソリューション名はソリューションルートのディレクトリ名としています。
14行目
sn.eeで作成したキーペアファイルはソリューションルートにあり、ファイル名はソリューション名.snkとしています。
41行目
公開キーの書き込まれるCore.csproj内のノードはPublicKeyとしています。

ビルド

.NET ToolのビルドはNuGetパッケージを作成します。

Releaseビルドで実行します。

windows:vs:vs-powershell.png

パッケージのビルド

Visual Studioのツールバーから『ビルド(B)』➡『パッケージProduct(P)』でNuGetパッケージを作成します。

インストール

開発者用PowerShellでビルドしたNuGetパッケージをグローバルインストールします。

windows:vs:vs-powershell.png

開発者用PowerShellの起動

Visual Studioのツールバーから『ツール(T)』➡『コマンドライン(L)』➡『開発者用PowerShell(P)』で開発者用PowerShellを起動します。

dotnet tool install --global --add-source .\Product\bin\Release ProgressLLC.Tools.SetPublicKey

1行目
add-sourceでパッケージの場所を指定します。
ProgressLLC.Tools.SetPublicKeyがインストールするパッケージ名です。

アップグレードは、ビルド後、下記のコマンドで行います。
.csprojVersionを必ず上げてからビルドします。

dotnet tool update --global ProgressLLC.Tools.SetPublicKey

1行目
最新versionのパッケージにアップグレードされます。
ProgressLLC.Tools.SetPublicKeyがアップグレードするパッケージ名です。

windows/vs/tools/setpublickey.txt · 最終更新: by プログレス合同会社