2.10.7: changelog; release.ps1 signs releases
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,15 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2.10.7
|
||||||
|
|
||||||
|
**Sicherheit**
|
||||||
|
- Daten von Servern und Mitspielern werden auf gültige Längen geprüft – eine gefälschte Längenangabe ließ das Spiel
|
||||||
|
vorher mit „Out of Memory“ abstürzen
|
||||||
|
- Mitspieler können nicht mehr beliebige Spiel-Zustände bei anderen auslösen (z.B. am eigenen Spieler oder an
|
||||||
|
Systemen wie Tod/Speichern) – nur noch Klicks auf klickbare Dinge und echte Schraub-/Montage-Schritte
|
||||||
|
- Uhrzeit/Wetter und Meldungen im Stil von Server-/Admin-Nachrichten („* …“) nur noch vom Admin bzw. Host
|
||||||
|
- Release ist mit dem MSCO-Release-Schlüssel signiert
|
||||||
|
|
||||||
## 2.10.6
|
## 2.10.6
|
||||||
|
|
||||||
- Neuer Konsolenbefehl **oilcheck**: zeigt, woher Öl ausläuft – die Leck-Werte des Motors (Ölwanne, Ölfilter,
|
- Neuer Konsolenbefehl **oilcheck**: zeigt, woher Öl ausläuft – die Leck-Werte des Motors (Ölwanne, Ölfilter,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ namespace MSCO
|
|||||||
/// Version der Mod. Kleine Updates: nur die dritte Stelle erhöhen (2.9.1, 2.9.2 ...) - sie bleiben mit
|
/// Version der Mod. Kleine Updates: nur die dritte Stelle erhöhen (2.9.1, 2.9.2 ...) - sie bleiben mit
|
||||||
/// Servern und Mitspielern derselben ersten zwei Stellen kompatibel.
|
/// Servern und Mitspielern derselben ersten zwei Stellen kompatibel.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const string ModVersion = "2.10.6";
|
public const string ModVersion = "2.10.7";
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Netzwerk-Version (die ersten beiden Stellen). Nur erhöhen, wenn sich das Protokoll inkompatibel ändert - dann
|
/// Netzwerk-Version (die ersten beiden Stellen). Nur erhöhen, wenn sich das Protokoll inkompatibel ändert - dann
|
||||||
/// auch GameServer.ModVersion im Server.
|
/// auch GameServer.ModVersion im Server.
|
||||||
|
|||||||
+60
-3
@@ -6,11 +6,18 @@
|
|||||||
# %AppData%\MSCO\release-token.dpapi (Windows DPAPI, nur dein Windows-Konto kann ihn lesen - anlegen mit
|
# %AppData%\MSCO\release-token.dpapi (Windows DPAPI, nur dein Windows-Konto kann ihn lesen - anlegen mit
|
||||||
# "release.ps1 -SaveToken"), sonst als Umgebungsvariable MSCO_RELEASE_TOKEN. Ohne Token wird nichts hochgeladen.
|
# "release.ps1 -SaveToken"), sonst als Umgebungsvariable MSCO_RELEASE_TOKEN. Ohne Token wird nichts hochgeladen.
|
||||||
# MSCO_NO_RELEASE=1 schaltet das Hochladen ab (Test-Builds).
|
# MSCO_NO_RELEASE=1 schaltet das Hochladen ab (Test-Builds).
|
||||||
|
#
|
||||||
|
# Signatur: jede Datei wird mit dem privaten Release-Schlüssel signiert (RSA-3072, SHA-256) - der Launcher und das Setup
|
||||||
|
# installieren nur, was zu ihrem eingebauten öffentlichen Schlüssel passt. Selbst wer den Keymaster oder den Token
|
||||||
|
# übernimmt, kann so keine fremde Datei als MSCO-Update verteilen. Der Schlüssel liegt DPAPI-verschlüsselt in
|
||||||
|
# %AppData%\MSCO\release-signing-key.dpapi (einmal anlegen: "release.ps1 -NewSigningKey"; SICHERN: "-ExportSigningKey").
|
||||||
# Gibt es die Version auf dem Keymaster schon, passiert nichts - also einfach die Version erhöhen und bauen.
|
# Gibt es die Version auf dem Keymaster schon, passiert nichts - also einfach die Version erhöhen und bauen.
|
||||||
param(
|
param(
|
||||||
[ValidateSet("mod", "launcher", "installer")][string]$Product,
|
[ValidateSet("mod", "launcher", "installer")][string]$Product,
|
||||||
[string]$File,
|
[string]$File,
|
||||||
[switch]$SaveToken,
|
[switch]$SaveToken,
|
||||||
|
[switch]$NewSigningKey,
|
||||||
|
[string]$ExportSigningKey = "",
|
||||||
[string]$Version = "",
|
[string]$Version = "",
|
||||||
[string]$Changelog = ""
|
[string]$Changelog = ""
|
||||||
)
|
)
|
||||||
@@ -36,7 +43,39 @@ if ($SaveToken) {
|
|||||||
Write-Host "Release token saved encrypted in $tokenDpapi"
|
Write-Host "Release token saved encrypted in $tokenDpapi"
|
||||||
exit 0
|
exit 0
|
||||||
}
|
}
|
||||||
if (-not $Product -or -not $File) { Write-Host "Usage: release.ps1 -Product mod|launcher|installer -File <file> | release.ps1 -SaveToken"; exit 1 }
|
$keyEntropy = [Text.Encoding]::UTF8.GetBytes("MSCO release signing key v1")
|
||||||
|
$keyFile = Join-Path $env:APPDATA "MSCO\release-signing-key.dpapi"
|
||||||
|
|
||||||
|
# Neues Schlüsselpaar (nur einmal - der öffentliche Schlüssel ist in Launcher und Setup eingebaut)
|
||||||
|
if ($NewSigningKey) {
|
||||||
|
if (Test-Path $keyFile) { Write-Host "There already is a signing key ($keyFile) - not replaced"; exit 1 }
|
||||||
|
$rsa = New-Object System.Security.Cryptography.RSACng 3072
|
||||||
|
$private = $rsa.ToXmlString($true)
|
||||||
|
New-Item -ItemType Directory -Force (Split-Path $keyFile) | Out-Null
|
||||||
|
[IO.File]::WriteAllBytes($keyFile, [Security.Cryptography.ProtectedData]::Protect([Text.Encoding]::UTF8.GetBytes($private), $keyEntropy, $scope))
|
||||||
|
Write-Host "Signing key saved encrypted in $keyFile"
|
||||||
|
Write-Host "PUBLIC KEY (put into Launcher/Setup):"
|
||||||
|
Write-Host $rsa.ToXmlString($false)
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
# Sicherung des privaten Schlüssels mit eigenem Passwort (AES-256, PBKDF2) - z.B. für einen USB-Stick/Passwort-Manager
|
||||||
|
if ($ExportSigningKey) {
|
||||||
|
if (-not (Test-Path $keyFile)) { Write-Host "No signing key"; exit 1 }
|
||||||
|
$private = [Security.Cryptography.ProtectedData]::Unprotect([IO.File]::ReadAllBytes($keyFile), $keyEntropy, $scope)
|
||||||
|
$pw = Read-Host "Password for the backup" -AsSecureString
|
||||||
|
$plainPw = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($pw))
|
||||||
|
if ($plainPw.Length -lt 12) { Write-Host "Use at least 12 characters"; exit 1 }
|
||||||
|
$salt = New-Object byte[] 16; (New-Object Security.Cryptography.RNGCryptoServiceProvider).GetBytes($salt)
|
||||||
|
$kdf = New-Object Security.Cryptography.Rfc2898DeriveBytes($plainPw, $salt, 300000, [Security.Cryptography.HashAlgorithmName]::SHA256)
|
||||||
|
$aes = [Security.Cryptography.Aes]::Create(); $aes.Key = $kdf.GetBytes(32); $aes.GenerateIV()
|
||||||
|
$enc = $aes.CreateEncryptor().TransformFinalBlock($private, 0, $private.Length)
|
||||||
|
$out = "MSCO-SIGNING-KEY-BACKUP v1`n" + [Convert]::ToBase64String($salt) + "`n" + [Convert]::ToBase64String($aes.IV) + "`n" + [Convert]::ToBase64String($enc)
|
||||||
|
[IO.File]::WriteAllText($ExportSigningKey, $out)
|
||||||
|
Write-Host "Backup written to $ExportSigningKey (AES-256, PBKDF2-SHA256 300000) - keep it and the password safe"
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $Product -or -not $File) { Write-Host "Usage: release.ps1 -Product mod|launcher|installer -File <file> | -SaveToken | -NewSigningKey | -ExportSigningKey <file>"; exit 1 }
|
||||||
|
|
||||||
if ($env:MSCO_NO_RELEASE -eq "1") { Write-Host "MSCO release: MSCO_NO_RELEASE=1 - not uploading."; exit 0 }
|
if ($env:MSCO_NO_RELEASE -eq "1") { Write-Host "MSCO release: MSCO_NO_RELEASE=1 - not uploading."; exit 0 }
|
||||||
$token = $env:MSCO_RELEASE_TOKEN
|
$token = $env:MSCO_RELEASE_TOKEN
|
||||||
@@ -82,6 +121,25 @@ if (-not $Changelog) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Signieren: "MSCO-RELEASE|<produkt>|<version>|<sha256>" - so passt die Signatur nur zu genau dieser Datei, diesem
|
||||||
|
# Produkt und dieser Version (keine alte Version als neue, keine Mod-DLL als Launcher)
|
||||||
|
$bytes = [System.IO.File]::ReadAllBytes((Resolve-Path $File))
|
||||||
|
$sha = [BitConverter]::ToString([Security.Cryptography.SHA256]::Create().ComputeHash($bytes)).Replace("-", "").ToLowerInvariant()
|
||||||
|
$signature = ""
|
||||||
|
if (Test-Path $keyFile) {
|
||||||
|
try {
|
||||||
|
$private = [Text.Encoding]::UTF8.GetString([Security.Cryptography.ProtectedData]::Unprotect([IO.File]::ReadAllBytes($keyFile), $keyEntropy, $scope))
|
||||||
|
$rsa = New-Object System.Security.Cryptography.RSACng
|
||||||
|
$rsa.FromXmlString($private)
|
||||||
|
$message = [Text.Encoding]::UTF8.GetBytes("MSCO-RELEASE|$Product|$Version|$sha")
|
||||||
|
$signature = [Convert]::ToBase64String($rsa.SignData($message, [Security.Cryptography.HashAlgorithmName]::SHA256, [Security.Cryptography.RSASignaturePadding]::Pkcs1))
|
||||||
|
$private = $null
|
||||||
|
} catch { Write-Host "MSCO release: signing failed ($($_.Exception.Message)) - not uploading"; exit 0 }
|
||||||
|
} else {
|
||||||
|
Write-Host "MSCO release: no signing key ($keyFile) - the launcher will refuse this release. Not uploading."
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
Add-Type -AssemblyName System.Net.Http
|
Add-Type -AssemblyName System.Net.Http
|
||||||
$client = New-Object System.Net.Http.HttpClient
|
$client = New-Object System.Net.Http.HttpClient
|
||||||
$client.Timeout = [TimeSpan]::FromMinutes(10)
|
$client.Timeout = [TimeSpan]::FromMinutes(10)
|
||||||
@@ -92,10 +150,9 @@ $boundary = "----MSCO" + [Guid]::NewGuid().ToString("N")
|
|||||||
$utf8 = New-Object System.Text.UTF8Encoding($false)
|
$utf8 = New-Object System.Text.UTF8Encoding($false)
|
||||||
$body = New-Object System.IO.MemoryStream
|
$body = New-Object System.IO.MemoryStream
|
||||||
function Write-Text([string]$text) { $b = $utf8.GetBytes($text); $body.Write($b, 0, $b.Length) }
|
function Write-Text([string]$text) { $b = $utf8.GetBytes($text); $body.Write($b, 0, $b.Length) }
|
||||||
foreach ($field in @(@("product", $Product), @("version", $Version), @("changelog", $Changelog))) {
|
foreach ($field in @(@("product", $Product), @("version", $Version), @("changelog", $Changelog), @("signature", $signature))) {
|
||||||
Write-Text "--$boundary`r`nContent-Disposition: form-data; name=`"$($field[0])`"`r`nContent-Type: text/plain; charset=utf-8`r`n`r`n$($field[1])`r`n"
|
Write-Text "--$boundary`r`nContent-Disposition: form-data; name=`"$($field[0])`"`r`nContent-Type: text/plain; charset=utf-8`r`n`r`n$($field[1])`r`n"
|
||||||
}
|
}
|
||||||
$bytes = [System.IO.File]::ReadAllBytes((Resolve-Path $File))
|
|
||||||
Write-Text "--$boundary`r`nContent-Disposition: form-data; name=`"file`"; filename=`"$([System.IO.Path]::GetFileName($File))`"`r`nContent-Type: application/octet-stream`r`n`r`n"
|
Write-Text "--$boundary`r`nContent-Disposition: form-data; name=`"file`"; filename=`"$([System.IO.Path]::GetFileName($File))`"`r`nContent-Type: application/octet-stream`r`n`r`n"
|
||||||
$body.Write($bytes, 0, $bytes.Length)
|
$body.Write($bytes, 0, $bytes.Length)
|
||||||
Write-Text "`r`n--$boundary--`r`n"
|
Write-Text "`r`n--$boundary--`r`n"
|
||||||
|
|||||||
Reference in New Issue
Block a user