Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,10 @@ build/
.env
.venv
venv/

# Material de firma de código: NUNCA subir claves privadas ni certificados
signing/
*.pfx
*.cer
*.p12
*.key
46 changes: 43 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,11 @@ necesitas instalar Python ni tener conexión a internet.**
1. Consigue el archivo **`Margoth_Setup.exe`** (te lo entrega quien distribuye la
aplicación).
2. Haz doble clic en `Margoth_Setup.exe`.
3. Si Windows muestra el aviso azul *"Windows protegió tu PC"* (aparece porque
el instalador aún no está firmado digitalmente), haz clic en **"Más
información"** y luego en **"Ejecutar de todas formas"**.
3. Si Windows muestra el aviso azul *"Windows protegió tu PC"*, haz clic en
**"Más información"** y luego en **"Ejecutar de todas formas"**.
> Este aviso no aparece si antes se instaló en el equipo el certificado de
> confianza de Margoth (ver *Firma de código* en la sección de Desarrollo).
> En una implementación gestionada, quien instala la app ya lo dejó listo.
4. Sigue el asistente y pulsa *Instalar*. **No pide permisos de administrador**:
se instala en tu carpeta de usuario.
5. Al terminar, abre **Margoth** desde el acceso directo del **Escritorio** o
Expand Down Expand Up @@ -192,6 +194,44 @@ El ejecutable se generará en `dist/Margoth/`.
2. Abrir `margoth_installer.iss` en Inno Setup.
3. Compilar para obtener `dist/Margoth_Setup.exe`.

### Firma de código (equipos internos)

La app y el instalador se firman con un certificado **auto-firmado**. Esto
sirve para distribución interna (equipos que tú controlas): tras confiar el
certificado en cada equipo, la firma es válida y desaparece el "Editor
desconocido".

> ⚠️ Un certificado auto-firmado **no** elimina SmartScreen para usuarios
> externos/públicos. Para eso se necesita un certificado **EV** comprado
> (~US$300-600/año, con token). El pipeline de abajo funciona igual con un
> cert comprado: solo cambia el certificado usado.

**Preparación (una sola vez, en el equipo de build):**
```powershell
# Crea el certificado y exporta signing\Margoth-CodeSigning.{cer,pfx}
tools\New-CodeSigningCert.ps1 -PfxPassword "<una-contraseña>"
```
El `.pfx` (clave privada) es tu respaldo: guárdalo a salvo, **nunca lo subas**
(la carpeta `signing/` está en `.gitignore`).

**En cada release:**
```powershell
python build_exe.py # 1. genera dist\Margoth\
tools\sign.ps1 -AppOnly # 2. firma Margoth.exe
ISCC margoth_installer.iss # 3. empaqueta el .exe ya firmado
tools\sign.ps1 -InstallerOnly # 4. firma Margoth_Setup.exe
```
Ambas firmas incluyen sello de tiempo RFC3161 (siguen válidas tras expirar el
certificado).

**En cada equipo donde se instale Margoth (una vez):**
```powershell
# Reparte signing\Margoth-CodeSigning.cer (es público) y ejecútalo allí:
tools\Trust-MargothCert.ps1
```
Windows pedirá confirmar la instalación del certificado raíz (es normal).
Después, `Margoth_Setup.exe` mostrará al editor **Carlos G** como válido.

## Principios de Diseño

- **Cero sobrecarga cognitiva**: Interfaces minimalistas para pacientes (Teoría de Mayer)
Expand Down
63 changes: 63 additions & 0 deletions tools/New-CodeSigningCert.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<#
.SYNOPSIS
Crea un certificado de firma de código AUTO-FIRMADO para Margoth.

.DESCRIPTION
Uso interno: firmar Margoth para equipos que tú controlas. NO sirve para
quitar SmartScreen en distribución pública (eso requiere un certificado EV
comprado). El certificado queda en el almacén del usuario (Cert:\CurrentUser\My)
y se exportan dos archivos en la carpeta `signing/`:

- Margoth-CodeSigning.cer -> PÚBLICO. Se instala en los equipos destino
para "confiar" en la firma.
- Margoth-CodeSigning.pfx -> PRIVADO (con contraseña). Respáldalo; permite
firmar desde otro equipo. NO se sube al repo.

Correr una sola vez. Válido por 5 años.

.PARAMETER PfxPassword
Contraseña para proteger el .pfx exportado (respaldo de la clave privada).

.PARAMETER Publisher
Nombre del editor (CN del certificado). Debe coincidir con AppPublisher del
instalador para que la identidad sea consistente.
#>
param(
[Parameter(Mandatory = $true)]
[string]$PfxPassword,

[string]$Publisher = "Carlos G",

[int]$YearsValid = 5
)

$ErrorActionPreference = "Stop"

$root = Split-Path -Parent $PSScriptRoot
$outDir = Join-Path $root "signing"
New-Item -ItemType Directory -Force -Path $outDir | Out-Null

Write-Host "Creando certificado de firma de código para '$Publisher'..."
$cert = New-SelfSignedCertificate `
-Type CodeSigningCert `
-Subject "CN=$Publisher" `
-FriendlyName "Margoth Code Signing" `
-CertStoreLocation "Cert:\CurrentUser\My" `
-KeyUsage DigitalSignature `
-KeyExportPolicy Exportable `
-NotAfter (Get-Date).AddYears($YearsValid)

Write-Host " Thumbprint: $($cert.Thumbprint)"

$cerPath = Join-Path $outDir "Margoth-CodeSigning.cer"
$pfxPath = Join-Path $outDir "Margoth-CodeSigning.pfx"

Export-Certificate -Cert $cert -FilePath $cerPath | Out-Null
$secure = ConvertTo-SecureString -String $PfxPassword -Force -AsPlainText
Export-PfxCertificate -Cert $cert -FilePath $pfxPath -Password $secure | Out-Null

Write-Host ""
Write-Host "Listo:"
Write-Host " Público (repartir a equipos): $cerPath"
Write-Host " Privado (respaldar, NO subir): $pfxPath"
Write-Host " Thumbprint para firmar: $($cert.Thumbprint)"
45 changes: 45 additions & 0 deletions tools/Trust-MargothCert.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<#
.SYNOPSIS
Instala el certificado público de Margoth como CONFIABLE en este equipo.

.DESCRIPTION
Correr UNA vez en cada equipo donde se vaya a instalar Margoth. Importa
`Margoth-CodeSigning.cer` a:
- Entidades de certificación raíz de confianza (valida la cadena)
- Editores de confianza (reconoce al editor)

Tras esto, la firma de Margoth.exe y de Margoth_Setup.exe aparece como
VÁLIDA y desaparece el aviso de "Editor desconocido".

NOTA: al importar a la raíz de confianza, Windows mostrará un cuadro de
seguridad pidiendo confirmación. Es normal: acepta para completar.

.PARAMETER CerPath
Ruta al archivo .cer público. Por defecto, junto a este script en signing\.

.PARAMETER AllUsers
Instala para TODOS los usuarios del equipo (requiere ejecutar como
administrador). Sin este switch, se instala solo para el usuario actual.
#>
param(
[string]$CerPath,
[switch]$AllUsers
)

$ErrorActionPreference = "Stop"

if (-not $CerPath) {
$root = Split-Path -Parent $PSScriptRoot
$CerPath = Join-Path $root "signing\Margoth-CodeSigning.cer"
}
if (-not (Test-Path $CerPath)) {
throw "No se encontró el certificado: $CerPath"
}

$scope = if ($AllUsers) { "LocalMachine" } else { "CurrentUser" }
Write-Host "Instalando confianza del certificado ($scope) desde: $CerPath"

Import-Certificate -FilePath $CerPath -CertStoreLocation "Cert:\$scope\Root" | Out-Null
Import-Certificate -FilePath $CerPath -CertStoreLocation "Cert:\$scope\TrustedPublisher" | Out-Null
Comment thread
Owito marked this conversation as resolved.

Write-Host "Listo. El certificado de Margoth ahora es de confianza en este equipo."
64 changes: 64 additions & 0 deletions tools/sign.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<#
.SYNOPSIS
Firma (Authenticode) el ejecutable de Margoth y/o el instalador.

.DESCRIPTION
Usa el certificado de firma de código del almacén del usuario
(Cert:\CurrentUser\My) sin necesidad de signtool ni de la contraseña del
.pfx. Agrega sello de tiempo RFC3161 para que la firma siga siendo válida
después de que el certificado expire.

Por defecto firma ambos artefactos si existen:
- dist\Margoth\Margoth.exe (la app)
- dist\Margoth_Setup.exe (el instalador)

Flujo recomendado de release:
1. python build_exe.py # genera dist\Margoth\
2. tools\sign.ps1 -AppOnly # firma Margoth.exe
3. ISCC margoth_installer.iss # empaqueta el .exe YA firmado
4. tools\sign.ps1 -InstallerOnly # firma Margoth_Setup.exe

.PARAMETER Subject
CN del certificado a usar (por defecto "Carlos G").

.PARAMETER TimestampServer
Servidor de sello de tiempo RFC3161.
#>
param(
[string]$Subject = "Carlos G",
[string]$TimestampServer = "http://timestamp.digicert.com",
[switch]$AppOnly,
[switch]$InstallerOnly
)
Comment thread
Owito marked this conversation as resolved.

$ErrorActionPreference = "Stop"
$root = Split-Path -Parent $PSScriptRoot

$cert = Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert |
Where-Object { $_.Subject -eq "CN=$Subject" } |
Sort-Object NotAfter -Descending |
Select-Object -First 1
Comment thread
Owito marked this conversation as resolved.

if (-not $cert) {
throw "No se encontró un certificado de firma con CN=$Subject. Corre primero tools\New-CodeSigningCert.ps1."
}
Write-Host "Firmando con: $($cert.Subject) [$($cert.Thumbprint)]"

$targets = @()
if (-not $InstallerOnly) { $targets += (Join-Path $root "dist\Margoth\Margoth.exe") }
if (-not $AppOnly) { $targets += (Join-Path $root "dist\Margoth_Setup.exe") }

foreach ($file in $targets) {
if (-not (Test-Path $file)) {
Write-Host " (omitido, no existe) $file"
continue
}
$res = Set-AuthenticodeSignature -FilePath $file -Certificate $cert `
-TimestampServer $TimestampServer -HashAlgorithm SHA256
Write-Host " $($res.Status) -> $file"
if ($res.Status -ne "Valid") {
throw "La firma de $file falló: $($res.StatusMessage)"
}
}

Write-Host "Firma completada."
Loading