# 🔐 Azure Trusted Signing Tool > Script tự động ký số (code signing) Windows executables bằng **Azure Trusted Signing** — không cần Azure CLI, không cần browser login. ## Mục lục - [Tổng quan](#tổng-quan) - [Yêu cầu](#yêu-cầu) - [Cấu hình Azure](#cấu-hình-azure) - [Cài đặt](#cài-đặt) - [Sử dụng](#sử-dụng) - [Tích hợp CI/CD](#tích-hợp-cicd) - [Troubleshooting](#troubleshooting) --- ## Tổng quan Tool này sử dụng: - **`signtool.exe`** (Windows SDK) — công cụ ký số chuẩn của Microsoft - **`Microsoft.Trusted.Signing.Client`** dlib — thư viện Azure Trusted Signing - **Service Principal** (App Registration) — xác thực tự động, không cần tương tác ### Kiến trúc ``` sign-app.ps1 │ ├── Đọc AZURE_CLIENT_ID / SECRET / TENANT_ID ├── Tạo metadata.json (BOM-free) │ └── signtool.exe sign /dlib Azure.CodeSigning.Dlib.dll /dmdf metadata.json │ └── Azure Trusted Signing API (eus.codesigning.azure.net) └── Ký hash → trả về signature → nhúng vào .exe ``` --- ## Yêu cầu | Thành phần | Version | Ghi chú | |-----------|---------|---------| | Windows | 10/11 hoặc Server 2019+ | Bắt buộc | | PowerShell | 5.1+ | Có sẵn trên Windows | | Windows SDK | 10.0.26100+ | Chứa `signtool.exe` | | .NET SDK | 8.0+ | Để build project | --- ## Cấu hình Azure ### Tài nguyên đã cấu hình | Thông số | Giá trị | |---------|---------| | Tenant ID | `c4f27370-f4da-4e92-b944-6adc120b3683` | | Trusted Signing Endpoint | `https://eus.codesigning.azure.net/` | | Account Name | `huanld` | | Certificate Profile | `codesign-profile` | | Trust Type | Private Trust | | App Registration | `trusted-signing-sp` | | Client ID | `0a2888b4-baa5-48da-abea-24f2e9a1f92f` | | Secret Expires | 2028-05-17 | ### Role Assignment Service Principal `trusted-signing-sp` có role: - **Trusted Signing Certificate Profile Signer** trên account `huanld` ### Setup Azure (lần đầu tiên) 1. **Tạo Trusted Signing Account** tại [Azure Portal](https://portal.azure.com): - Resource Group: `signcode` - Account Name: `huanld` - Region: `East US` 2. **Tạo Certificate Profile**: - Profile Name: `codesign-profile` - Profile Type: `Private Trust` 3. **Tạo App Registration**: ``` Azure Portal → App registrations → New registration Name: trusted-signing-sp ``` 4. **Tạo Client Secret**: ``` App registration → Certificates & secrets → New client secret Expiry: 24 months ``` 5. **Gán Role**: ``` Trusted Signing Account → Access control (IAM) → Add role assignment Role: Trusted Signing Certificate Profile Signer Member: trusted-signing-sp ``` --- ## Cài đặt ### 1. Tải Microsoft.Trusted.Signing.Client Script tự động tải nếu chưa có. Hoặc tải thủ công: ```powershell $dir = "$env:TEMP\TrustedSigningClient" New-Item -ItemType Directory -Force -Path $dir | Out-Null Invoke-WebRequest "https://www.nuget.org/api/v2/package/Microsoft.Trusted.Signing.Client" ` -OutFile "$dir\TrustedSigningClient.zip" -UseBasicParsing Expand-Archive "$dir\TrustedSigningClient.zip" -DestinationPath $dir -Force ``` ### 2. Xác nhận signtool.exe ```powershell Get-ChildItem "C:\Program Files (x86)\Windows Kits\10\bin\*\x86\signtool.exe" | Sort-Object FullName -Descending | Select-Object -First 1 ``` Kết quả mong đợi: ``` C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x86\signtool.exe ``` --- ## Sử dụng ### Cú pháp ```powershell powershell -ExecutionPolicy Bypass -File sign-app.ps1 -FilePath <đường_dẫn_file> ``` ### Ví dụ ```powershell # Ký file exe đơn powershell -ExecutionPolicy Bypass -File sign-app.ps1 -FilePath "C:\build\MyApp.exe" # Ký file trong thư mục publish powershell -ExecutionPolicy Bypass -File sign-app.ps1 ` -FilePath "D:\Code\MyProject\bin\Release\net8.0\publish\MyApp.exe" # Ký nhiều file (loop) Get-ChildItem "D:\build\publish" -Filter "*.exe" | ForEach-Object { powershell -ExecutionPolicy Bypass -File sign-app.ps1 -FilePath $_.FullName } ``` ### Tham số tùy chỉnh ```powershell powershell -ExecutionPolicy Bypass -File sign-app.ps1 ` -FilePath "C:\build\MyApp.exe" ` -AccountUrl "https://eus.codesigning.azure.net/" ` -AccountName "huanld" ` -ProfileName "codesign-profile" ` -TimestampUrl "http://timestamp.acs.microsoft.com" ` -SigntoolPath "C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x86\signtool.exe" ``` ### Output ``` [INFO] Signing: C:\build\MyApp.exe Account : huanld / codesign-profile Trusted Signing Version: 1.0.95 Submitting digest for signing... OperationId abc123: InProgress Signing completed with status 'Succeeded' in 2.3s Successfully signed: C:\build\MyApp.exe [OK] Signed successfully! Status : Valid Signer : CN=huanleducoutlook.onmicrosoft.com, ... Expires : 2026-06-18 ``` ### Xác nhận chữ ký ```powershell $sig = Get-AuthenticodeSignature "C:\build\MyApp.exe" $sig | Format-List Status, SignerCertificate ``` --- ## Tích hợp CI/CD ### PowerShell Build Script ```powershell # build-and-sign.ps1 param([string]$Configuration = "Release") $projectPath = "D:\Code\MyProject" $signScript = "$PSScriptRoot\sign-app.ps1" # Build dotnet publish $projectPath -c $Configuration -o "$projectPath\publish" # Sign all executables Get-ChildItem "$projectPath\publish" -Filter "*.exe" | ForEach-Object { Write-Host "Signing: $($_.Name)" & powershell -ExecutionPolicy Bypass -File $signScript -FilePath $_.FullName if ($LASTEXITCODE -ne 0) { throw "Signing failed for $($_.Name)" } } Write-Host "All files signed successfully!" ``` ### GitHub Actions / Gitea Actions ```yaml - name: Sign Executables shell: pwsh env: AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }} AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} run: | powershell -ExecutionPolicy Bypass -File sign-app.ps1 ` -FilePath "${{ github.workspace }}\publish\MyApp.exe" ``` > ⚠️ Trong CI/CD, đặt credentials vào **Secrets** thay vì hardcode trong script. --- ## Troubleshooting ### ❌ `SignerSign() failed` / BOM error **Nguyên nhân**: metadata.json có BOM (Byte Order Mark). **Giải pháp**: Script đã xử lý tự động bằng `UTF8Encoding(false)`. ### ❌ `Selected user account does not exist in tenant 'Microsoft Services'` **Nguyên nhân**: Dùng sai client ID (Azure CLI app `04b07795...`). **Giải pháp**: Dùng App Registration riêng trong tenant của bạn. ### ❌ `The request body must contain 'code'` (AADSTS900144) **Nguyên nhân**: Dùng OAuth2 v1 endpoint với sai tên parameter. **Giải pháp**: Dùng v2.0 endpoint + hashtable body (đã sửa trong script hiện tại). ### ❌ `Signing failed` với status `UnknownError` **Nguyên nhân**: Certificate Profile là **Private Trust** — chỉ verify được trong môi trường tin tưởng nội bộ. **Giải pháp**: Upgrade lên **Public Trust** để distribute ra ngoài (yêu cầu địa chỉ tổ chức tại Mỹ/EU và Microsoft vetting). ### ❌ `signtool.exe not found` ```powershell # Tìm signtool.exe Get-ChildItem "C:\Program Files (x86)\Windows Kits" -Filter "signtool.exe" -Recurse | Sort-Object FullName -Descending | Select-Object -First 1 FullName ``` Nếu không có, cài Windows SDK từ: ``` https://developer.microsoft.com/en-us/windows/downloads/windows-sdk/ ``` ### ❌ Role assignment chưa có hiệu lực Role mới gán cần **5-10 phút** để propagate. Chờ rồi thử lại. --- ## Tham khảo - [Azure Trusted Signing Docs](https://learn.microsoft.com/en-us/azure/trusted-signing/) - [Microsoft.Trusted.Signing.Client NuGet](https://www.nuget.org/packages/Microsoft.Trusted.Signing.Client) - [signtool.exe Reference](https://learn.microsoft.com/en-us/windows/win32/seccrypto/signtool) - [Azure Identity - DefaultAzureCredential](https://learn.microsoft.com/en-us/dotnet/api/azure.identity.defaultazurecredential)