Stufe 4c (#764): Kommentare in C#, WebUI-Skripten, CSS und HTML auf Englisch
Rund 2.600 Kommentarblöcke in 310 C#-Dateien übersetzt, Begründungen vollständig erhalten: blockweise gleiche Anzahl wie vorher, kein Block unter 60 % seiner Länge, der Code außerhalb der Kommentare per Tokenizer byte-identisch geprüft. Zitate deutscher Oberflächentexte und Testdaten bleiben deutsch. WebUI: Kommentare in JS/CSS/HTML übersetzt, JSDoc-@param an die in 4b umbenannten Parameter angeglichen. Dabei behoben: - 4b hatte "tag" (HTML-Tag) als deutsches "Tag" gelesen und zu "dayValue" gemacht (makeElement, svg, Tagesübersicht) - zurück auf tag/day. - Aufgaben "InProgress" bekamen die Plakettenart "laufend", für die es nie eine CSS-Klasse gab - jetzt "success" wie laufende Instanzen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
da4ceac7d5
commit
837a634a22
@@ -6,13 +6,13 @@ using Shouldly;
|
||||
namespace ClawdDotNet.Core.Tests.Security;
|
||||
|
||||
/// <summary>
|
||||
/// Der Schlüsselbund: ein Hauptschlüssel, mehrfach verpackt (#190).
|
||||
/// The keyring: one master key, wrapped several times (#190).
|
||||
///
|
||||
/// <para><b>Worum es geht.</b> Ein Hauptschlüssel, der an die Maschine gebunden ist,
|
||||
/// überlebt keinen Umzug — eine Sicherung auf einem neuen Rechner enthält dann lauter
|
||||
/// Werte, die niemand mehr lesen kann. Einer, der an eine Passphrase gebunden ist,
|
||||
/// überlebt den Umzug, verlangt aber bei jedem Start jemanden, der tippt. Der Bund löst
|
||||
/// das, indem er sich nicht entscheidet.</para>
|
||||
/// <para><b>What this is about.</b> A master key that is bound to the machine
|
||||
/// survives no move — a backup on a new machine then contains nothing but
|
||||
/// values nobody can read any more. One bound to a passphrase
|
||||
/// survives the move, but demands someone who types at every start. The keyring solves
|
||||
/// that by not deciding.</para>
|
||||
/// </summary>
|
||||
public sealed class SecretKeyRingTests : IDisposable
|
||||
{
|
||||
@@ -26,16 +26,16 @@ public sealed class SecretKeyRingTests : IDisposable
|
||||
public void Dispose()
|
||||
{
|
||||
try { Directory.Delete(_dir, recursive: true); }
|
||||
catch { /* Ein liegengebliebener Testordner faerbt keinen Lauf rot. */ }
|
||||
catch { /* A leftover test folder does not turn a run red. */ }
|
||||
}
|
||||
|
||||
private static SecretKeyRing Bund(params ISecretProvider[] anbieter) => new(anbieter);
|
||||
|
||||
/// <summary>
|
||||
/// Der Kern der Sache: Zwei Fächer, ein Schlüssel.
|
||||
/// The heart of the matter: two slots, one key.
|
||||
///
|
||||
/// <para>Führten sie zu verschiedenen Schlüsseln, wäre der ganze Aufbau wertlos — der
|
||||
/// Wiederherstellungsweg öffnete dann zwar etwas, aber nicht die vorhandenen Werte.</para>
|
||||
/// <para>If they led to different keys, the whole setup would be worthless — the
|
||||
/// recovery path would then open something, but not the existing values.</para>
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Zwei_Faecher_fuehren_zu_demselben_Schluessel()
|
||||
@@ -46,17 +46,17 @@ public sealed class SecretKeyRingTests : IDisposable
|
||||
var schluessel = bund.Load(Pfad);
|
||||
bund.SetRecoveryPassphrase(Pfad, schluessel, "geheim");
|
||||
|
||||
// Nur mit der Passphrase, ohne das Maschinenfach - der Umzug.
|
||||
// Only with the passphrase, without the machine slot - the move.
|
||||
var nachUmzug = Bund(PassphraseSecretProvider.With("geheim")).Load(Pfad);
|
||||
|
||||
nachUmzug.ShouldBe(schluessel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Der Fall, um den es bei #190 geht: Die Maschine wechselt.
|
||||
/// The case #190 is about: the machine changes.
|
||||
///
|
||||
/// <para>Das Maschinenfach geht nicht mehr auf. Ohne Wiederherstellungsfach wären
|
||||
/// hier alle Zugangsdaten verloren.</para>
|
||||
/// <para>The machine slot no longer opens. Without a recovery slot
|
||||
/// all credentials would be lost here.</para>
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Nach_einem_Maschinenwechsel_oeffnet_die_Passphrase()
|
||||
@@ -71,12 +71,12 @@ public sealed class SecretKeyRingTests : IDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Und danach läuft der Alltag wieder ohne Passphrase: Das Maschinenfach wird auf der
|
||||
/// neuen Maschine neu angelegt.
|
||||
/// And afterwards day-to-day operation runs without a passphrase again: the machine slot is
|
||||
/// created anew on the new machine.
|
||||
///
|
||||
/// <para>Das setzt voraus, dass die neue Maschine überhaupt ein Fach anzubieten hat.
|
||||
/// Auf einem System, dessen einziger Weg <c>file</c> wäre, geschieht es
|
||||
/// <em>nicht</em> — siehe <see cref="Ein_Umzug_legt_den_Schluessel_nicht_im_Klartext_ab"/>.</para>
|
||||
/// <para>That presupposes that the new machine has a slot to offer at all.
|
||||
/// On a system whose only way would be <c>file</c>, it does
|
||||
/// <em>not</em> happen — see <see cref="Ein_Umzug_legt_den_Schluessel_nicht_im_Klartext_ab"/>.</para>
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Nach_dem_Umzug_entsteht_das_Maschinenfach_neu()
|
||||
@@ -85,22 +85,22 @@ public sealed class SecretKeyRingTests : IDisposable
|
||||
var schluessel = bund.Load(Pfad);
|
||||
bund.SetRecoveryPassphrase(Pfad, schluessel, "umzug");
|
||||
|
||||
// Neue Maschine mit funktionierendem Maschinenfach: Es oeffnet zwar noch nicht,
|
||||
// aber die Passphrase tut es - und danach wird es angelegt.
|
||||
// New machine with a working machine slot: it does not open yet,
|
||||
// but the passphrase does - and afterwards the slot is created.
|
||||
var neueMaschine = Bund(new MaschinenFach(), PassphraseSecretProvider.With("umzug"));
|
||||
neueMaschine.Load(Pfad).ShouldBe(schluessel);
|
||||
|
||||
// Ab jetzt reicht das Maschinenfach allein, ohne Passphrase.
|
||||
// From now on the machine slot alone suffices, without a passphrase.
|
||||
Bund(new MaschinenFach()).Load(Pfad).ShouldBe(schluessel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ein Umzug legt den Hauptschlüssel <b>nicht</b> im Klartext ab, nur damit der
|
||||
/// nächste Start bequemer wird.
|
||||
/// A move does <b>not</b> store the master key in plain text just so that the
|
||||
/// next start is more convenient.
|
||||
///
|
||||
/// <para>Auf einem System ohne sicheres Maschinenfach — also Linux vor #180 — bleibt
|
||||
/// die Passphrase der Weg. Ein <c>file</c>-Fach danebenzulegen, machte genau den
|
||||
/// Schutz zunichte, für den sich jemand gerade entschieden hat.</para>
|
||||
/// <para>On a system without a secure machine slot — i.e. Linux before #180 — the
|
||||
/// passphrase stays the way. Putting a <c>file</c> slot next to it would destroy exactly the
|
||||
/// protection someone has just chosen.</para>
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Ein_Umzug_legt_den_Schluessel_nicht_im_Klartext_ab()
|
||||
@@ -117,8 +117,8 @@ public sealed class SecretKeyRingTests : IDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Eine falsche Passphrase öffnet nichts — und erzeugt vor allem keinen neuen
|
||||
/// Schlüssel.
|
||||
/// A wrong passphrase opens nothing — and above all does not generate a new
|
||||
/// key.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Eine_falsche_Passphrase_oeffnet_nichts()
|
||||
@@ -133,12 +133,12 @@ public sealed class SecretKeyRingTests : IDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <b>Die wichtigste Zusage überhaupt.</b> Geht kein Fach auf, wird abgebrochen —
|
||||
/// nicht stillschweigend ein neuer Schlüssel erzeugt.
|
||||
/// <b>The most important promise of all.</b> If no slot opens, it aborts —
|
||||
/// rather than silently generating a new key.
|
||||
///
|
||||
/// <para>Ein neuer Hauptschlüssel machte jeden bestehenden <c>enc:v2</c>-Wert
|
||||
/// unlesbar, und zwar wortlos: Die Anwendung liefe weiter und meldete lauter kaputte
|
||||
/// Zugangsdaten, ohne dass jemand den Zusammenhang sähe.</para>
|
||||
/// <para>A new master key would make every existing <c>enc:v2</c> value
|
||||
/// unreadable, and silently so: the application would keep running and report nothing but broken
|
||||
/// credentials without anyone seeing the connection.</para>
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Wenn_kein_Fach_aufgeht_wird_abgebrochen_statt_neu_erzeugt()
|
||||
@@ -152,8 +152,8 @@ public sealed class SecretKeyRingTests : IDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Der Hauptschlüssel darf in der Datei nirgends im Klartext stehen — erst das macht
|
||||
/// sie sicherungsfähig.
|
||||
/// The master key must not stand anywhere in plain text in the file — only that makes
|
||||
/// it fit for backups.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Mit_Passphrase_steht_der_Schluessel_nirgends_im_Klartext()
|
||||
@@ -171,14 +171,14 @@ public sealed class SecretKeyRingTests : IDisposable
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
// Mit einem Maschinenfach faellt das ungeschuetzte weg.
|
||||
// With a machine slot the unprotected one drops away.
|
||||
inhalt.ShouldNotContain("\"file\"");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ein Bund ohne Wiederherstellungsweg funktioniert tadellos — und versagt an dem
|
||||
/// einen Tag vollständig. Deshalb wird er vorher benannt.
|
||||
/// A keyring without a recovery path works flawlessly — and fails completely on that
|
||||
/// one day. That's why it is named beforehand.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Ein_Bund_ohne_Wiederherstellung_meldet_das()
|
||||
@@ -204,7 +204,7 @@ public sealed class SecretKeyRingTests : IDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ohne Passphrase entsteht kein Wiederherstellungsfach — es gibt ja nichts, womit.
|
||||
/// Without a passphrase no recovery slot comes about — there is nothing to make it with.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Ohne_Passphrase_gibt_es_kein_Wiederherstellungsfach()
|
||||
@@ -217,40 +217,40 @@ public sealed class SecretKeyRingTests : IDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Steht eine Passphrase bereit, wird sie genommen — auch beim nachträglichen Öffnen
|
||||
/// eines Bundes, der noch keines hat.
|
||||
/// If a passphrase is available, it is used — even when later opening
|
||||
/// a keyring that does not have one yet.
|
||||
///
|
||||
/// <para>Wer <c>CLAWD_SECRET_PASSPHRASE</c> setzt, hat das mit Absicht getan. Das
|
||||
/// Fach nicht anzulegen hieße, ihn zu ignorieren und ihm anschließend vorzuhalten, er
|
||||
/// möge doch eine Passphrase hinterlegen.</para>
|
||||
/// <para>Whoever sets <c>CLAWD_SECRET_PASSPHRASE</c> did so on purpose. Not creating the
|
||||
/// slot would mean ignoring them and then reproaching them that they
|
||||
/// should please store a passphrase.</para>
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Eine_bereitstehende_Passphrase_wird_nachtraeglich_uebernommen()
|
||||
{
|
||||
// Erst ohne: Es entsteht nur das ungeschuetzte Fach.
|
||||
// First without: only the unprotected slot comes about.
|
||||
Bund(new PlainFileSecretProvider(), new PassphraseSecretProvider(() => null)).Load(Pfad);
|
||||
|
||||
// Dann mit: Das Wiederherstellungsfach kommt hinzu, das ungeschuetzte faellt weg.
|
||||
// Then with: the recovery slot is added, the unprotected one drops away.
|
||||
var mit = Bund(new PlainFileSecretProvider(), PassphraseSecretProvider.With("spaeter"));
|
||||
var schluessel = mit.Load(Pfad);
|
||||
|
||||
mit.Slots.ShouldContain(s => s.Provider == "passphrase");
|
||||
|
||||
// Das ungeschuetzte Fach bleibt: Der automatische Weg nimmt nichts weg. Es
|
||||
// wegzuraeumen, weil gerade eine Umgebungsvariable gesetzt ist, waere eine
|
||||
// unumkehrbare Aenderung, die niemand verlangt hat.
|
||||
// The unprotected slot stays: the automatic path takes nothing away. Removing
|
||||
// it because an environment variable happens to be set would be an
|
||||
// irreversible change nobody asked for.
|
||||
mit.Slots.ShouldContain(s => s.Provider == "file");
|
||||
mit.Problems.ShouldContain(p => p.Contains("ungeschützt"));
|
||||
|
||||
// Und es ist derselbe Schluessel wie vorher.
|
||||
// And it is the same key as before.
|
||||
Bund(PassphraseSecretProvider.With("spaeter")).Load(Pfad).ShouldBe(schluessel);
|
||||
}
|
||||
|
||||
// ─── Übernahme der Altformate ───
|
||||
// ─── Taking over the legacy formats ───
|
||||
|
||||
/// <summary>
|
||||
/// Der Linux-Bestand: 32 rohe Byte in der Datei. Wer aktualisiert, soll nichts
|
||||
/// merken — derselbe Schlüssel, nur anders verpackt.
|
||||
/// The Linux legacy: 32 raw bytes in the file. Whoever updates should notice
|
||||
/// nothing — the same key, just wrapped differently.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Ein_roher_Altschluessel_wird_uebernommen()
|
||||
@@ -262,8 +262,8 @@ public sealed class SecretKeyRingTests : IDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Und nach der Übernahme steht das Fächerformat in der Datei — der alte Rohschlüssel
|
||||
/// ist weg, der Wert derselbe.
|
||||
/// And after the takeover the slot format is in the file — the old raw key
|
||||
/// is gone, the value the same.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Nach_der_Uebernahme_steht_das_Faecherformat_in_der_Datei()
|
||||
@@ -279,13 +279,13 @@ public sealed class SecretKeyRingTests : IDisposable
|
||||
datei.Version.ShouldBe(3);
|
||||
datei.Slots.ShouldContain(s => s.Provider == "passphrase");
|
||||
|
||||
// Und der Wert hat sich nicht geaendert - sonst waeren alle enc:v2-Werte hin.
|
||||
// And the value has not changed - otherwise all enc:v2 values would be lost.
|
||||
Bund(PassphraseSecretProvider.With("neu")).Load(Pfad).ShouldBe(alt);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Eine Datei falscher Länge ist kaputt und wird nicht ersetzt: Das machte alle
|
||||
/// verschlüsselten Werte unlesbar, ohne dass jemand erführe, warum.
|
||||
/// A file of the wrong length is broken and is not replaced: that would make all
|
||||
/// encrypted values unreadable without anyone finding out why.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Eine_beschaedigte_Datei_wird_nicht_stillschweigend_ersetzt()
|
||||
@@ -295,7 +295,7 @@ public sealed class SecretKeyRingTests : IDisposable
|
||||
Should.Throw<CryptographicException>(() => Bund(new PlainFileSecretProvider()).Load(Pfad));
|
||||
}
|
||||
|
||||
/// <summary>Ein Maschinenfach, das hier funktioniert — der Ersatz fuer DPAPI im Test.</summary>
|
||||
/// <summary>A machine slot that works here — the substitute for DPAPI in the test.</summary>
|
||||
private sealed class MaschinenFach : ISecretProvider
|
||||
{
|
||||
public string Name => "dpapi";
|
||||
@@ -310,7 +310,7 @@ public sealed class SecretKeyRingTests : IDisposable
|
||||
=> slot.Provider == Name ? Convert.FromBase64String(slot.Payload) : null;
|
||||
}
|
||||
|
||||
/// <summary>Ein Fach, das nie aufgeht — die neue Maschine, das fremde Benutzerkonto.</summary>
|
||||
/// <summary>A slot that never opens — the new machine, the foreign user account.</summary>
|
||||
private sealed class NieOeffnendesFach : ISecretProvider
|
||||
{
|
||||
public string Name => "dpapi";
|
||||
|
||||
Reference in New Issue
Block a user