|
2025-11-04
2025-11-11
概要 †ビルドしたwindows用のexeにデジタル署名(コードサイニング証明書)を付与する手順を記載する。 目次 †
デジタル署名とは †デジタル署名とは Windows の SmartScreen やセキュリティ警告を回避するために、
認証局 †コードサイニング証明書の種類と費用 †
主な認証局(2025年現在) †
※金額は年額 個人開発者が使う場合 †個人でも利用出来て、比較的安価なのは Certum の Standard Code Signing ぐらい? EV証明書であれば SmartScreen 警告は出なくなるが、EV証明書は法人用の為、個人では利用できない。 証明書のタイムスタンプについて †コードサイニング証明書には有効期限があるが、 主なタイムスタンプサーバ
自己署名 †前述の通り、個人開発者であれば上記の認証局を使用しても、即時に SmartScreen警告は回避出来ない。 そこで、ここでは自分でCAを立てて署名する手順を記載する。(結局はすぐに警告は回避できないので) 自己証明書作成 †以前に Javaでhttps通信時の証明書検証について で作成した証明書作成用のシェルを今回用に少し弄って使用する。 make_cert.sh #!/bin/bash rm -rf *.key rm -rf *.csr rm -rf *.crt cat <<_MY_CONF_ > mycert.cnf [ req ] default_bits = 2048 default_md = sha256 prompt = no encrypt_key = no distinguished_name = dn [ dn ] C = JP O = My Company CN = My Code Signing _MY_CONF_ # 証明書要求の作成 openssl req -new -config mycert.cnf -keyout myapp.key -out myapp.csr # 署名 openssl x509 -days 365 -req -signkey myapp.key -extfile mycert.cnf < myapp.csr > myapp.crt # PFX形式に変換 openssl pkcs12 -passin pass:"" -passout pass:"" -export -out myapp.pfx -inkey myapp.key -in myapp.crt # 作成した証明書の内容を表示 openssl x509 -noout -text -in myapp.pfx exeへの署名 †Windows環境でWindows用に署名 †Windowsの場合、signtool で署名する。
Mac/Linux環境でWindows用に署名 †
プログラムによる署名検証 †pythonによる署名検証(手っ取り早くPowerShellを使用する) import subprocess
import json
def get_signature_info(exe_path: str):
ps_script = f"""
$sig = Get-AuthenticodeSignature '{exe_path}';
$obj = [PSCustomObject]@{{
Status = $sig.Status.ToString();
Signer = $sig.SignerCertificate.Subject;
Issuer = $sig.SignerCertificate.Issuer;
Thumbprint = $sig.SignerCertificate.Thumbprint;
}};
$obj | ConvertTo-Json -Compress
"""
result = subprocess.run(["powershell", "-Command", ps_script], capture_output=True, text=True)
if result.returncode != 0:
raise RuntimeError("署名情報の取得に失敗しました")
info = json.loads(result.stdout)
return info
if __name__ == "__main__":
exe = "dist/app.exe"
info = get_signature_info(exe)
print("署名検証結果:")
print(json.dumps(info, indent=2, ensure_ascii=False))
# 署名者(Signer)、証明書ハッシュ(Thumbprint) などを照合
if "My Company" in info["Signer"]:
print("OK")
else:
print("NG")
(結果例) 署名検証結果:
{
"Status": "UnknownError",
"Signer": "O=My Company, L=xxxx, S=xxxx, C=JP, CN=xxx.xxx.xxx",
"Issuer": "CN=xxx.xxx.xxx, O=xxxx, L=xxxx, S=xxxx, C=JP",
"Thumbprint": "81CD829A58A13480C...."
}
OK
補足 †PowerShellを使用せずに署名を参照/チェックする場合のサンプルコード。 import pefile
import sys
from cryptography import x509
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.serialization import pkcs7
def get_exe_thumbprint(path: str) -> str:
pe = pefile.PE(path)
dir_entry = pe.OPTIONAL_HEADER.DATA_DIRECTORY[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_SECURITY']]
if dir_entry.VirtualAddress == 0:
raise ValueError("署名が存在しません。")
data = bytes(pe.write())
offset = dir_entry.VirtualAddress
end = offset + dir_entry.Size
thumbprint = None
while offset < end:
# 証明書格納位置を判定し情報取得
# https://learn.microsoft.com/windows/win32/debug/pe-format#the-attribute-certificate-table-image-only
dw_length = int.from_bytes(data[offset:offset + 4], "little")
cert_type = int.from_bytes(data[offset + 6:offset + 8], "little") # 2=WIN_CERT_TYPE_PKCS_SIGNED_DATA
blob = data[offset + 8: offset + dw_length]
if cert_type == 2:
certs = pkcs7.load_der_pkcs7_certificates(blob)
def has_code_signing_eku(c: x509.Certificate) -> bool:
# Code Signing EKU を持つ証明書を優先(なければ先頭)
try:
eku = c.extensions.get_extension_for_oid(
x509.oid.ExtensionOID.EXTENDED_KEY_USAGE
).value
return x509.oid.ExtendedKeyUsageOID.CODE_SIGNING in eku
except x509.ExtensionNotFound:
return False
target = next((c for c in certs if has_code_signing_eku(c)), certs[0] if certs else None)
if target:
print("Subject:", target.subject.rfc4514_string())
print("Issuer :", target.issuer.rfc4514_string())
thumbprint = target.fingerprint(hashes.SHA1()).hex().upper()
print("Thumbprint (SHA1):", thumbprint)
break # 目的がThumbprint取得ならここで十分
# 8バイト境界にアライン
offset = (offset + ((dw_length + 7) & ~7))
if not thumbprint:
raise ValueError("証明書を取得できませんでした。")
return thumbprint
if __name__ == "__main__":
exe_path = "dist/myapp_server.exe"
try:
thumb = get_exe_thumbprint(exe_path)
print("Thumbprint取得成功:", thumb)
except Exception as e:
tb = sys.exc_info()[2]
print("署名取得エラー:{}".format(e.with_traceback(tb)))
|