LayerX エンジニアブログ

LayerX の エンジニアブログです。

GitHub ActionsでElectronアプリケーションをコード署名する

バクラク事業部でソフトウェアエンジニアをしている @ta1m1kam です。

最近、Electronでデスクトップアプリケーションを作成する必要がありました。Electronアプリケーションを本番環境で配布する際、コード署名は避けて通れないステップです。しかし、macOSやWindowsのコード署名に関する情報は断片的で、実際の本番環境での実装例はなかなか見つかりません。

そこで本記事では、GitHub Actionsを使ったElectronアプリケーションのコード署名方法について解説します。私自身も多くの方のテックブログに助けられてリリースまで辿り着けたため、同じように課題に直面した方の助けになるよう、コード署名のステップをまとめて記録することにしました。本記事ではビルドツールとしてelectron-builderを使用しています。

www.electron.build

なぜコード署名が必要なのか

現在、macOSやWindowsでは未署名アプリをインストールしようとすると、以下のような警告が表示されます。

コード署名は「このアプリを誰が作ったのか」「配布中に改ざんされていないか」をユーザーとOSに示す仕組みです。これにより警告が緩和され、インストール体験をスムーズにすることができます。

未署名アプリダウンロード時のアラート

www.electronjs.org

macOS編

必要な証明書と準備

macOSでのコード署名に必要なもの:

項目 説明 取得方法
Developer ID Application証明書 アプリケーション署名用の証明書(.p12形式) Apple Developer Portalから取得
Apple API Key Notarization用のAPIキー App Store Connect APIから生成
API Key ID APIキーの識別子 APIキー生成時に取得
Issuer ID 組織の識別子 App Store Connectで確認

Apple Store Connect API Key

electron-builderでは以下の3つの認証方法が利用可能ですが、App Store Connect API Key方式が推奨されています。

利用可能な認証方法:

  1. App Store Connect API Key (推奨)
  2. Apple ID + App-specific password
  3. Keychain profile

App-specific password はApple ID全体へのアクセス権を持ってしまい、メールや連絡先など所属する全チームの情報にアクセスできてしまうため、必要以上に権限が大きくセキュリティリスクがあります。一方、App Store Connect API Keyはスコープを特定のチームに制限でき、個人情報にアクセスすることもないため推奨されています。

詳細は以下のGitHub Issueを確認してみてください。

github.com

electron-builder.yml設定(macOS)

ここではdmg形式でのサンプルを記載します。

appId: com.app.your
productName: "Your App"
mac:
  hardenedRuntime: true # Notarizationに必須(falseだとエラー)
  entitlements: build/entitlements.mac.plist
  entitlementsInherit: build/entitlements.mac.plist
dmg:
  artifactName: YourApp-${version}.${ext}
macキー
  • hardenedRuntime : Apple 公証 (notarization) に必須。有効にすると macOS の実行時セキュリティ制御(Hardened Runtime)が有効化される
  • entitlements: 親アプリに付与する権限 (entitlements) の定義ファイル。例: JIT を許可したり、ライブラリ検証を無効にしたりする設定
  • entitlementsInherit: 子プロセス(Electron Helper)に付与する権限の定義ファイル(例ではentitlements と同じファイルを指定している)
dmgキー
  • artifactName: 出力されるインストーラ(DMG)のファイル名テンプレート

GitHub Actionsでの自動化(macOS)

Github ActionsでmacOS向けにコード署名を行うworkflowのステップ例です。

必要なCSC_LINK(base64エンコードされたp12ファイル), APPLE_API_KEY, APPLE_API_ISSUERなどはGitHub Secretに登録しておきます。 electron-builderを使用している場合は APPLE_API_KEY などの環境変数を自動的に読み取り、コード署名に利用してくれます

jobs:
  build-app:
    runs-on: macos-latest

    steps:
      - name: Decode and setup certificate for mac
        env:
          CSC_LINK: ${{ secrets.csc-link }}
          APPLE_API_KEY: ${{ secrets.apple-api-key }}
        run: |
          WORKSPACE_DIR=$(pwd)
          # Base64エンコードされた証明書をデコード
          echo "$CSC_LINK" | base64 --decode > $WORKSPACE_DIR/certificate.p12
          echo "CSC_LINK=$WORKSPACE_DIR/certificate.p12" >> $GITHUB_ENV
          # Apple API Keyをデコード(.p8ファイル)
          echo "$APPLE_API_KEY" | base64 --decode > $WORKSPACE_DIR/AuthKey.p8
          echo "APPLE_API_KEY=$WORKSPACE_DIR/AuthKey.p8" >> $GITHUB_ENV

      - name: Build mac
        env:
          CSC_FOR_PULL_REQUEST: true
          CSC_KEY_PASSWORD: ${{ secrets.csc-key-password }}
          # Electron BuilderのNotarization用環境変数
          # <https://www.electron.build/mac.html#notarize>
          APPLE_API_KEY: ${{ env.APPLE_API_KEY }} # .p8ファイルのパス
          APPLE_API_KEY_ID: ${{ secrets.apple-api-key-id }} # APIキーのID(例:9ABCDEF123)
          APPLE_API_ISSUER: ${{ secrets.apple-api-issuer }} # Issuer ID(例:12345678-1234-1234-1234-123456789012)
        run: |
          VERSION="${{ inputs.electron-app-build-version }}"
          # macOS用ビルド(Notarizationあり)
          pnpm build:mac:${{ inputs.env }} -c.extraMetadata.version="${VERSION}"
        working-directory: ./apps/${{ inputs.app-name }}

Windows編

Windowsのコード署名には、コード署名証明書を提供するベンダーとの契約が必要です。 現在、Windows向けのデスクトップアプリケーション配布にはEV署名が求められますが、ほとんどのベンダーは物理キー(USB)による提供のみでした。そこで私は DigiCert KeyLocker を利用しました。KeyLockerはクラウドベースでのEV署名をサポートしており、安全にコード署名を行えるうえ、CI/CDでの実装ドキュメントも充実しています。 knowledge.digicert.com

AzureのTrusted Signingも検討しましたが、当時はUSまたはCanadaのみのサポートだったため断念しました。

Trusted Signing Public Preview Update | Microsoft Community Hub

必要な証明書と準備

WindowsでのDigiCert KeyLocker署名に必要なもの(DigiCert KeyLocker GitHub Actions

DigiCertから取得する必要があるアクセスキーや証明書の発行の仕方はもドキュメントに書いてあります。

項目 説明 取得方法
SM API Key KeyLocker APIアクセスキー DigiCert Portalで生成
クライアント証明書 API認証用証明書(.p12形式) DigiCert Portalからダウンロード
署名証明書のSHA1ハッシュ 証明書のフィンガープリント DigiCert Portalで確認

electron-builder.yml設定(Windows)

ここではnsis形式でのサンプルを記載します。

appId: com.app.your
productName: "Your App"
afterAllArtifactBuild: scripts/afterAllArtifactBuild.js # 署名フック
win:
  executableName: Your-App
nsis:
  artifactName: Your-App-${version}-setup.${ext}
  shortcutName: ${productName}
  uninstallDisplayName: ${productName}
  • afterAllArtifactBuild: ビルド完了後にカスタム処理(署名やアップロード)をするフック
winキー
  • executableName: Windows 実行ファイル (.exe) 名
nsisキー
  • artifactName: インストーラー (.exe) ファイル名
  • shortcutName: ショートカットの表示名
  • uninstallDisplayName: アンインストーラ表示名

afterAllArtifactBuildフック実装

Windows環境で生成された setup.exe を対象に signtool.exe を用いてコード署名を行います。環境変数で指定された証明書情報を使って署名し、その後 signtool verify によって署名を検証しています。

const { execSync } = require("node:child_process");
const fs = require("node:fs");
const path = require("node:path");

exports.default = async function afterAllArtifactBuild(context) {
  const { artifactPaths, outDir } = context;

  console.log("afterAllArtifactBuild called");
  console.log(`Process platform: ${process.platform}`);

  // Windowsの場合のみ実行
  if (process.platform !== "win32") {
    console.log("Skipping code signing - not Windows");
    return;
  }

  console.log("Starting Windows setup.exe code signing process...");

  // 環境変数の確認
  const requiredEnvVars = [
    "SM_HOST",
    "SM_API_KEY",
    "SM_CLIENT_CERT_FILE",
    "SM_CLIENT_CERT_PASSWORD",
    "SM_CODE_SIGNING_CERT_SHA1_HASH",
  ];

  for (const envVar of requiredEnvVars) {
    if (!process.env[envVar]) {
      throw new Error(`Required environment variable ${envVar} is not set`);
    }
  }

  // setup.exeファイルを検索
  const setupFiles = artifactPaths.filter((filePath) => {
    const fileName = path.basename(filePath);
    return (
      fileName.includes("setup") &&
      fileName.endsWith(".exe") &&
      !fileName.endsWith(".blockmap")
    );
  });

  if (setupFiles.length === 0) {
    console.log("No setup.exe files found in artifacts");
    return;
  }

  for (const filePath of setupFiles) {
    const fileStats = fs.statSync(filePath);
    console.log(`Signing file: ${filePath} (size: ${fileStats.size} bytes)`);

    try {
      // signtoolでコード署名を実行
      // DigiCertガイド推奨のパラメータを使用
      // 参考: <https://docs.digicert.com/ja/digicert-keylocker/sign-with-digicert-signing-tools/sign-with-signtool.html>
      const signCommand = [
        "signtool.exe",
        "sign",
        `/sha1 ${process.env.SM_CODE_SIGNING_CERT_SHA1_HASH}`, // 証明書の指定
        "/tr <http://timestamp.digicert.com>", // タイムスタンプサーバー
        "/td SHA256", // タイムスタンプのダイジェストアルゴリズム
        "/fd SHA256", // ファイルのダイジェストアルゴリズム
        `"${filePath}"`,
      ].join(" ");

      console.log(`Executing: ${signCommand}`);
      execSync(signCommand, { stdio: "inherit" });

      // 署名の検証
      const verifyCommand = [
        "signtool.exe",
        "verify",
        "/v", // 詳細出力
        "/pa", // Authenticodeの検証
        `"${filePath}"`,
      ].join(" ");

      console.log(`Verifying: ${verifyCommand}`);
      execSync(verifyCommand, { stdio: "inherit" });

      console.log(`Successfully signed: ${path.basename(filePath)}`);
    } catch (err) {
      console.error(`Failed to sign ${path.basename(filePath)}:`, err.message);
      throw err;
    }
  }

  console.log("Windows setup.exe code signing completed successfully");
};

GitHub Actionsでの自動化(Windows)

Github ActionsでWindows向けにコード署名を行うworkflowのステップ例です。 Windows環境でElectronアプリをビルドする際、DigiCert KeyLockerを使った署名環境を準備し、秘密情報から証明書を復号・セットアップします。その後、DigiCertのKeyLocker Toolsをインストール・同期し、環境変数を設定してelectron-builderでWindows用インストーラーをビルドします。実際の署名処理は先ほどの afterAllArtifactBuild.js で実行されます。

jobs:
  build-app:
    runs-on: windows-latest
    steps:
      - name: Decode and setup certificate for windows
        shell: bash
        run: |
          # Base64エンコードされたクライアント証明書をデコード
          echo "${{ secrets.sm-client-cert-file-base64 }}" | base64 --decode > D:/Certificate_pkcs12.p12

      - name: Set variables for windows
        shell: bash
        run: |
          echo "KEYPAIR_NAME=gt-standard-keypair" >> $GITHUB_OUTPUT
          echo "CERTIFICATE_NAME=gt-certificate" >> $GITHUB_OUTPUT
          echo "C:\Program Files (x86)\Windows Kits\10\App Certification Kit" >> $GITHUB_PATH
          echo "C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools" >> $GITHUB_PATH
          echo "C:\Program Files\DigiCert\DigiCert Keylocker Tools" >> $GITHUB_PATH

      - name: Setup SSM KSP on windows latest
        shell: cmd
        run: |
          # DigiCert KeyLocker Toolsをダウンロード(KSP、PKCS11、smctlを含む)
          # 参考: <https://docs.digicert.com/ja/digicert-keylocker/ci-cd-integrations-and-deployment-pipelines/scripts/github.html>
          curl -X GET <https://one.digicert.com/signingmanager/api-ui/v1/releases/Keylockertools-windows-x64.msi/download> ^
            -H "x-api-key:${{ secrets.sm-api-key }}" ^
            -o Keylockertools-windows-x64.msi
          # インストール
          msiexec /i Keylockertools-windows-x64.msi /quiet /qn
          # KSP(Key Storage Provider)の登録と確認
          smctl windows ksp register
          smctl windows ksp list
          smctl.exe keypair ls
          # 証明書の同期
          smctl windows certsync

      - name: Build win
        shell: bash
        env:
          # Electron Builderでの自動署名を無効化
          CSC_IDENTITY_AUTO_DISCOVERY: false
          # DigiCert KeyLocker環境変数
          SM_HOST: ${{ secrets.sm-host }}
          SM_API_KEY: ${{ secrets.sm-api-key }}
          SM_CLIENT_CERT_FILE: ${{ env.SM_CLIENT_CERT_FILE }}
          SM_CLIENT_CERT_PASSWORD: ${{ secrets.sm-client-cert-password }}
          SM_CODE_SIGNING_CERT_SHA1_HASH: ${{ secrets.sm-code-signing-cert-sha1-hash }}
        run: |
          VERSION="${{ inputs.electron-app-build-version }}"
          # Windows用ビルド(afterSignフックで署名)
          pnpm build:win:${{ inputs.env }} -c.extraMetadata.version="${VERSION}"
        working-directory: ./apps/${{ inputs.app-name }}

まとめ

本記事では、Electronアプリケーションのコード署名をmacOSとWindows両方の環境で実装する方法を解説しました。 ニッチな内容ですが、Electronアプリケーションのコード署名で悩んでいる方の参考になれば幸いです。


今回の記事を読んで気になった方やLayerXについて知りたい方は、ぜひカジュアル面談でおしゃべりしましょう!

jobs.layerx.co.jp

採用情報についてはこちらです。 jobs.layerx.co.jp