Restructure into src/tests and add build foundation (M0)

Moves the scaffold to src/DodoSSH.Api and establishes the repo conventions the rest
of the milestones build on.

Structure:
- src/{Contracts,Crypto,Domain,Infrastructure,Api}, tests/{Contracts,Crypto,Domain}.Tests
- DodoSSH.slnx rewritten with src/ and tests/ solution folders

Build:
- Directory.Build.props centralises TFM, nullable, deterministic builds and
  TreatWarningsAsErrors; Directory.Packages.props pins every version centrally
- packages.lock.json committed so CI restores in locked mode
- NuGet.config clears machine-level sources, which both fixes NU1507 under central
  package management and makes restore reproducible off this machine
- Microsoft.OpenApi pinned to 2.11.0: ASP.NET Core 10.0.10 resolves 2.0.0, which is
  covered by GHSA-v5pm-xwqc-g5wc (high, patched in 2.7.5)

Analyzers:
- AnalysisLevel is Recommended, not All. With warnings-as-errors, All turns opinionated
  naming rules into build breaks and trains people to blanket-suppress.
- BannedSymbols.txt bans DateTime.UtcNow (TimeProvider), Guid.NewGuid (CreateVersion7),
  sync-over-async, MD5/SHA1, PBKDF2 and SecureString
- CA1711/CA1724 disabled: both are .NET Framework CAS-era naming rules
- PublicApiAnalyzers on Contracts only, since that assembly is the client's real contract

API:
- weather-forecast template removed
- UseHttpsRedirection removed; TLS terminates at the reverse proxy and redirecting
  behind one causes loops
- /healthz/{live,ready,startup}. Liveness deliberately checks no dependencies so a
  transient database outage cannot restart the container and kill live SSH sessions.

Notes:
- No coverage collector yet. Microsoft.Testing.Extensions.CodeCoverage pulls an MTP 1.x
  MSBuild extension that throws TypeLoadException against the MTP 2.3.x xunit.v3 brings.
  Coverage gates are an M3 concern; revisit with an MTP 2.x-aligned version then.

Verified: dotnet build (0 warnings), 17 tests pass, format check clean, API serves
health and OpenAPI endpoints.
This commit is contained in:
2026-07-28 12:25:34 +02:00
parent 1138291d79
commit 3a81f3c90b
43 changed files with 1714 additions and 62 deletions
+47
View File
@@ -0,0 +1,47 @@
namespace DodoSSH.Crypto;
/// <summary>
/// Constants of the DodoSSH cryptographic specification.
/// </summary>
/// <remarks>
/// docs/crypto.md is the normative specification; this type must agree with it exactly.
/// The implementation of the envelope, AAD derivation and key wrapping lands in M1, once
/// the specification and its test vectors are frozen. Nothing else may be built on top of
/// an unfrozen AAD: only clients can re-encrypt, so a change after users hold data cannot
/// be migrated server-side.
/// </remarks>
public static class CryptoSpec
{
/// <summary>Magic prefix identifying a DSH1 envelope.</summary>
public const string EnvelopeMagic = "DSH1";
/// <summary>Version of the AAD derivation rule that payloads are bound to.</summary>
/// <remarks>
/// Stored per row as <c>payload_aad_version</c> so a future change can be applied
/// lazily, re-encrypting on next write rather than in a migration.
/// </remarks>
public const short CurrentAadVersion = 1;
/// <summary>Domain-separation prefix for every AAD computation.</summary>
public const string AadDomainPrefix = "dsh1\n";
/// <summary>Identifiers for the algorithms an envelope may declare.</summary>
public enum AlgorithmId : byte
{
/// <summary>Reserved; never written.</summary>
Unspecified = 0,
/// <summary>Symmetric content encryption under a known key.</summary>
XChaCha20Poly1305 = 1,
/// <summary>Symmetric fallback where XChaCha20 is unavailable.</summary>
Aes256Gcm = 2,
/// <summary>Anonymous-sender seal to an X25519 public key.</summary>
SealToX25519 = 3,
// 4 is reserved for a hybrid X25519 + ML-KEM-768 seal. Store-now-decrypt-later is
// a genuine threat for long-lived SSH keys, so the identifier is claimed now even
// though the construction ships later.
}
}
+19
View File
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<!--
The DSH1 envelope format, AAD derivation, key wrapping and the KDF.
Referenced by both the API and the client, but the server only ever uses the format
and fingerprint constants: it never holds a key and never decrypts a payload.
See docs/crypto.md, which is the normative specification for everything here.
-->
<PropertyGroup>
<IsTrimmable>true</IsTrimmable>
<IsAotCompatible>true</IsAotCompatible>
</PropertyGroup>
<ItemGroup>
<InternalsVisibleTo Include="DodoSSH.Crypto.Tests" />
</ItemGroup>
</Project>
+25
View File
@@ -0,0 +1,25 @@
{
"version": 2,
"dependencies": {
"net10.0": {
"Meziantou.Analyzer": {
"type": "Direct",
"requested": "[3.0.134, )",
"resolved": "3.0.134",
"contentHash": "tTYCcYKyOko3TMNxmxmA9nakbcHVUgglENmCMIhzIjl9y9FBZO/0tWSxTGC74Sp198FmWih5S5KkjQRBg5ePkQ=="
},
"Microsoft.CodeAnalysis.BannedApiAnalyzers": {
"type": "Direct",
"requested": "[5.6.0, )",
"resolved": "5.6.0",
"contentHash": "Kcobt3pnOdO0A+6CKiMHZdTEluJpsfxiV20axtZdmfBQnDmiWTKPJADlgAfdTuKNAnVarrkJa0UEGwuOo91muw=="
},
"Microsoft.NET.ILLink.Tasks": {
"type": "Direct",
"requested": "[10.0.10, )",
"resolved": "10.0.10",
"contentHash": "f5VCIE7AJpd5YvzNTeMGVzQIgyE9tX+AreTYwQF+REbu+DZo/2Ae+jNSwhPEYrVz6RRkd7y8ubXjk6Nn6Ka+Cg=="
}
}
}
}