| | | 1 | | using ArturRios.Util.Hashing; |
| | | 2 | | |
| | | 3 | | namespace ArturRios.Heimdall.Command.Services; |
| | | 4 | | |
| | | 5 | | /// <summary> |
| | | 6 | | /// The Argon2id cost every password and code derivation in the process uses: the library's |
| | | 7 | | /// 600 MB / 16-lane default (NFR-18), unless <see cref="InsecureTestVariable" /> asks for the |
| | | 8 | | /// cheapest cost Argon2id accepts. |
| | | 9 | | /// </summary> |
| | | 10 | | /// <remarks> |
| | | 11 | | /// <para> |
| | | 12 | | /// The switch exists for the test suite. A derivation at the production cost is 600 MB, and |
| | | 13 | | /// the suite derives thousands of times: the CI agent's test process grew past its memory |
| | | 14 | | /// limit and was killed, and Command.Tests spent most of its time in Argon2id. Nothing a test |
| | | 15 | | /// asserts depends on the cost, only on derivation and verification agreeing, so the suite |
| | | 16 | | /// sets the variable in <c>tests/default.runsettings</c> and derives through |
| | | 17 | | /// <see cref="Configuration" /> everywhere, fixtures included. |
| | | 18 | | /// </para> |
| | | 19 | | /// <para> |
| | | 20 | | /// A switch rather than three numbers, and named for what it is, so that it cannot be half |
| | | 21 | | /// tuned: every stored hash can only be verified with the cost it was made with, so a changed |
| | | 22 | | /// cost locks every person out. Start-up refuses it in the environments Heimdall is |
| | | 23 | | /// deployed in, Production and homologation's Staging (<see cref="EnsureAllowed" />). |
| | | 24 | | /// </para> |
| | | 25 | | /// <para> |
| | | 26 | | /// Read once, when the type is first used: the decoy hash and every gate call must agree for |
| | | 27 | | /// the life of the process. |
| | | 28 | | /// </para> |
| | | 29 | | /// </remarks> |
| | | 30 | | public static class PasswordHashing |
| | | 31 | | { |
| | | 32 | | /// <summary>The environment variable that, set to <c>true</c>, selects <see cref="Insecure" />.</summary> |
| | | 33 | | public const string InsecureTestVariable = "HEIMDALL_INSECURE_TEST_PASSWORD_HASHING"; |
| | | 34 | | |
| | | 35 | | /// <summary>The cheapest cost Argon2id accepts: one lane, one pass, 8 KB. For tests only.</summary> |
| | 22 | 36 | | public static HashConfiguration Insecure { get; } = new(1, 1, 8); |
| | | 37 | | |
| | | 38 | | /// <summary>The cost in effect for this process.</summary> |
| | 1729 | 39 | | public static HashConfiguration Configuration { get; } = FromEnvironment(Environment.GetEnvironmentVariable); |
| | | 40 | | |
| | | 41 | | /// <summary> |
| | | 42 | | /// <see cref="Insecure" /> when <paramref name="read" /> gives <c>true</c> (ignoring case and |
| | | 43 | | /// surrounding space) for <see cref="InsecureTestVariable" />; the production cost for anything |
| | | 44 | | /// else, unset included. |
| | | 45 | | /// </summary> |
| | | 46 | | public static HashConfiguration FromEnvironment(Func<string, string?> read) => |
| | 10 | 47 | | string.Equals(read(InsecureTestVariable)?.Trim(), "true", StringComparison.OrdinalIgnoreCase) |
| | 10 | 48 | | ? Insecure |
| | 10 | 49 | | : new HashConfiguration(); |
| | | 50 | | |
| | | 51 | | /// <summary> |
| | | 52 | | /// Refuses <paramref name="configuration" /> when it is <see cref="Insecure" /> in an environment |
| | | 53 | | /// Heimdall is deployed in. |
| | | 54 | | /// </summary> |
| | | 55 | | /// <exception cref="InvalidOperationException"> |
| | | 56 | | /// <paramref name="isDeployed" /> and <paramref name="configuration" /> is <see cref="Insecure" />. |
| | | 57 | | /// </exception> |
| | | 58 | | public static void EnsureAllowed(bool isDeployed, HashConfiguration configuration) |
| | | 59 | | { |
| | 976 | 60 | | if (isDeployed && ReferenceEquals(configuration, Insecure)) |
| | | 61 | | { |
| | 1 | 62 | | throw new InvalidOperationException( |
| | 1 | 63 | | $"{InsecureTestVariable} is set in a deployed environment. Refusing to start: it makes " + |
| | 1 | 64 | | "every password hash trivial to brute-force, and no hash stored with it verifies once it is removed."); |
| | | 65 | | } |
| | 975 | 66 | | } |
| | | 67 | | } |