『コードサイニング証明書 署名ツール（eToken版）』を作成しました

## １．経緯
以前、EVコードサイニング証明書はダウンロードして「PFXファイル」で取得できましたが、
2021年6月1日以降は、eTokenでの発行のみとなったそうで、
今回更新の際には、SefeNet社のUSBドングルで提供されるようになり、
このeTokenを使わないとEVコードサイニングの署名が出来なくなりました。

例：
[(グローバルサイン)コードサイニング証明書の仕様変更に関するお知らせ](https://info-globalsign.com/news/20210517)
[(デジサート)【重要】コードサイニング証明書おける公開鍵長等の仕様変更について](https://knowledge.digicert.com/ja/jp/alerts/ALERT2757.html)


しかしこの、Windows SDKのsigntoolを利用して、Microsoft Authenticode署名を行う方法ですが、
多くのサポートサイトの情報では、従来の「PFXファイル」を使う方法しか紹介されていないのです。

[(マイクロソフト)SignTool を使用してファイルに署名する](https://learn.microsoft.com/ja-jp/windows/win32/seccrypto/using-signtool-to-sign-a-file)
>次のコマンドは、パスワードで保護された PFX ファイルに格納されている証明書を使用してファイルに署名します。
```SignTool sign /f MyCert.pfx /p MyPassword MyControl.exe```

>次のコマンドは、ファイルに署名し、タイムスタンプを設定します。
```SignTool sign /f MyCert.pfx /t http://timestamp.digicert.com MyControl.exe```


[(グローバルサイン)[コードサイニング証明書/EVコードサイニング証明書] Authenticode用 署名ツールの利用方法](https://jp.globalsign.com/support/codesign/manual/signature-tool.html)

[(デジサート)Signtoolでの署名方法](https://knowledge.digicert.com/ja/jp/solution/SO23053.html)



EVコードサイニング証明書発行元のサポート窓口へ問い合わせをして、
eTokenを接続して、SAC（SafeNet Authentication Client）でアクセスできる状態になった状態から、以下のような構文で署名できることは確認できました。
```signtool.exe sign /a /debug /tr http://[タイムスタンプサーバ] /fd sha256 /td sha256 apprication.exe```


この、署名対象ファイル選択→signtool.exe構文の入力を簡素化するために、署名ツールを作ります。
以前「PFXファイル」版の署名ツールは、ExcelVBAで作成していましたが、
今回はPythonで作ることにしました。

## ２．作成
<B>（１）まずはchatGPTにインプットしてベースのコードを作ってもらいました。（ほぼ想定通り動く！）</B>
<details><summary><font color="blue">【参考：以下クリックで展開】「コードサイニング証明書_署名ツール(eToken版)」の作成（chatGPTへのインプット）</font></summary>

~~~
「コードサイニング証明書_署名ツール(eToken版)」をpython3で作成してください。

#[前提条件]
- コードサイニング証明書はeToken（USBドングル）で提供されている。
- eTokenには「SafeNetAuthenticationClient」がインストールされたWindowsパソコンでアクセスできる。

#[変数]
${inFile} #eTokenにあるコードサイニング証明書(cerファイル）
${PASSWORD} #eTokenにあるSSL証明書のパスワード
${outFile} #署名対象ファイル

#[関数]
- function 証明書パスワードの入力
 - 初期値で「xxxxxxxx」がインプットボックスに入力されている。再入力で変更も可能。
 - ${PASSWORD}に格納する。

- function 署名対象ファイルを選択
 - 署名対象ファイル(exe, zip,など）を「ファイル選択ダイアログ」で選択する。
 - ${outFile}に格納する。

- function 電子署名の実行
 - 現カレント実行フォルダにある、ローカルに保存しておいた「signtool.exe」を呼び出して、次の工程を自動実行する。
  - 電子署名の削除
　　　選択されている、署名対象ファイルから、既存の電子署名を削除する。
　　　signtool.exe remove /s ${outFile}
　- SHA256署名の実行
　　　signtool.exe sign /a /tr http://timestamp.digicert.com /fd sha256 /td sha256 /p ${PASSWORD} ${outFile}

#[動作の説明]
- Windowsコマンドプロンプト上で動作する。必要に応じてGUIダイアログが表示される。
- 最初に、WindowsパソコンにeTokenが接続されているか？と、
「SafeNetAuthenticationClient」でコードサイニング証明書にアクセス出来る事を確認する。
　またその操作をユーザーに促す説明を表示する。
- 各関数の機能が実行される。
 - function 証明書パスワードの入力
 - function 署名対象ファイルを選択
 - function 電子署名の実行

以上。
~~~
</details>

<B>（２）chatGPTの出力から少し追記など行ったPython3コード</B>
- eToken利用では、パスワードをコマンドで指定できなかったので、証明書パスワードの入力を促すのみとした。
    （パスワードは実行ファイルと同名のiniに記載しておいたものを表示させるとした。）
- tkinterで作成したGUIを画面中央に表示するようにした。
- exe化した時にsigntool.exeの結果がそのままでは標準出力に表示出来なかったのでtkinterのメッセージボックスに表示するように変えた。
    （pythonコードのexe化はpyinstallerで実施。）

```コードサイニング証明書_署名ツール(eToken版).py
import tkinter as tk
from tkinter import filedialog
from tkinter import messagebox

import subprocess

import os
import configparser
inifile = configparser.ConfigParser()
filename = os.path.splitext(os.path.basename(__file__))[0]
#inifile.read(f'{filename}.ini', 'UTF-8')
inifile.read(f'{filename}.ini')
password = inifile.get('SIGNING_PASSWORD', 'PASSWORD')

def select_file():
    global outFile
    file_path = filedialog.askopenfilename()
    outFile = file_path

def execute_signing():
    remove_cmd = f'signtool.exe remove /s "{outFile}"'
    subprocess.run(remove_cmd, shell=True)

    sign_cmd = f'signtool.exe sign /a /tr http://timestamp.digicert.com /fd sha256 /td sha256 "{outFile}"'
    result = subprocess.run(sign_cmd, check=True, shell=True, stdout=subprocess.PIPE)
    messagebox.showinfo('実行結果', result.stdout)


## GUIの作成
root = tk.Tk()
root.title("Code Signing Tool")

## パスワードの表示
password_label = tk.Label(root, text="証明書パスワードを求められた際は\n次の値を入力してください")
password_label.pack(pady=5)
input_box = tk.Entry(root)
input_box.insert(tk.END, password)
input_box.pack()

## 署名対象ファイルの選択
file_button = tk.Button(root, text="ファイル選択", command=select_file)
file_button.pack(pady=20)

## 電子署名の実行
sign_button = tk.Button(root, text="電子署名実行", command=execute_signing)
sign_button.pack(pady=20)

## GUIを画面中央に
root.update()
ww, wh = root.winfo_width(), root.winfo_height()
wx0 = root.winfo_rootx() - root.winfo_x()
th = root.winfo_rooty() - root.winfo_y()
wh = wh + th
sw = root.winfo_screenwidth()
sh = root.winfo_screenheight()
root.wm_geometry('+'+str((int((sw-ww)/2))-wx0)+
        '+'+str(int((sh-wh)/2)))

root.mainloop()
```
<B>（３）起動用バッチファイル</B>
```コードサイニング証明書_署名ツール(eToken版)_起動.cmd
@echo off
chcp 65001
cd %~dp0
cls

echo '事前準備'
echo '（１）WindowsパソコンにeToken（USBドングル）が接続されていること。'
echo '（２）「SafeNet Authentication Client」がインストールされていること。'

rem python コードサイニング証明書_署名ツール(eToken版).py

start "" /wait コードサイニング証明書_署名ツール(eToken版).exe

pause
```

## ３．利用の様子
<B>（１）eTokenドングルを挿入する</B>
![2_USBドングルを挿す.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1667967/1ef73c5a-2547-a261-4149-994d4e28545c.png)

<B>（２）SAC（SafeNet Authentication Client）はインストール済み</B>
![1_SACはインストール済み.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1667967/a103cc46-057e-bf80-6dd2-b60b6fdddaf7.png)

<B>（３）「コードサイニング証明書_署名ツール(eToken版)」の実行の様子</B>
![3_「コードサイニング証明書_署名ツール(eToken版)」の実行の様子.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1667967/20b1c228-b726-a361-ddd7-6dd68afa2202.gif)

<B>（４）対象ファイルに「デジタル署名」が確認できる</B>
![4_デジタル署名される.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1667967/7fa7e1a0-6bb5-8018-4943-1e38fefb8a3d.png)


以上です。