Skip to content

SAIF.NetCore.SystemHash architecture

SAIF.NetCore.SystemHash is a single-class implementation-layer library in the Framework.NCore dependency graph that adapts SAIF.NetCore.HashUtil's reversible salting primitive into a caller-identity credential scheme used by the Environment API and OIDC crypto settings; it is one of the three documented implementation-to-implementation exceptions in Architecture.md because it takes a direct NuGet package dependency on SAIF.NetCore.HashUtil in addition to the foundation package SAIF.NetCore.ServiceContracts.

Responsibilities

  • Implement ISystemHashService (SAIF.NetCore.ServiceContracts) as the repository's sole concrete SystemHashService, producing a SHA-256/Base64 identity hash from a SystemType and system name.
  • Issue a salted, time-stamped credential (ComputeHash, Get) by delegating the salting mechanics to SAIF.NetCore.HashUtil's SaltShaker while supplying the embedded create timestamp itself.
  • Compute the unsalted identity hash on demand (ComputeHashNow) and de-salt plus validate the lifetime of a previously issued hash (RemoveSystemHashSaltAndValidateLifetime), throwing SystemHashExpiredException when expired.
  • Resolve the identity assembly used to build the hash (GetAssembly), defaulting to Assembly.GetCallingAssembly() unless an assembly was supplied to the constructor.

Component boundaries

  • SystemHashService: the only public type in the library; it owns the SHA-256/Base64 computation, the choice of DateTime.Now as the embedded create date, assembly-name resolution, and the lifetime/expiry decision.
  • Boundary with SAIF.NetCore.HashUtil: SystemHashService constructs SaltShaker directly (once with a DateTime to salt, once parameterless to de-salt) and constructs/throws SystemHashExpiredException; HashUtil owns only the salting/de-salting mechanics and never compares dates itself.
  • Boundary with SAIF.NetCore.ServiceContracts: the library implements ISystemHashService and consumes the SystemType enum as its sole input type; it has no other contract dependency.
  • Boundary with consumers: SAIF.NetCore.ServiceExtensions constructs SystemHashService(Assembly) and registers it as a singleton factory for ISystemHashService (used by RegisterSecureEnvironmentClient and inline by RegisterDBConnectionClient); SAIF.NetCore.EnvironmentClient and SAIF.NetCore.OIDC consume only the ISystemHashService interface, with no package reference to SystemHash itself.

Interaction model

  • Client-side credential issuance: a consumer calls Get(systemType) or ComputeHash(systemType, systemName); SystemHashService computes the SHA-256/Base64 hash, constructs SaltShaker(DateTime.Now), and calls SaltHash to produce the salted 120-character credential string.
  • Server-side validation: a consumer calls RemoveSystemHashSaltAndValidateLifetime(systemHashValue, expireSeconds, nowBufferSeconds); SystemHashService constructs a parameterless SaltShaker, calls BlandHash to recover the original hash and create date, compares the recovered date against the supplied lifetime, and either returns the de-salted value or throws SystemHashExpiredException.
  • There is no inbound or outbound network call anywhere in this library; both operations are in-process cryptographic/string computation backed by HashUtil.
  • ISystemHashService is reached by SAIF.NetCore.EnvironmentClient and SAIF.NetCore.OIDC purely through the SAIF.NetCore.ServiceContracts interface — neither library takes a package reference to SAIF.NetCore.SystemHash — so the only architectural NuGet edges into this library are from SAIF.NetCore.ServiceExtensions.

Architecture diagram

The following diagram shows SystemHash's position relative to its one implementation-layer exception dependency, the foundation layer, and the rest of the Framework.NCore dependency graph.

flowchart TB
    SC["SAIF.NetCore.ServiceContracts<br>(ISystemHashService, SystemType)"]
    HASH["SAIF.NetCore.HashUtil<br>(SaltShaker, SystemHashExpiredException)"]
    SYSHASH["SAIF.NetCore.SystemHash<br>(SystemHashService)"]
    SE["SAIF.NetCore.ServiceExtensions"]
    ENVCLIENT["SAIF.NetCore.EnvironmentClient<br>(ISystemHashService consumer, interface-only)"]
    OIDC["SAIF.NetCore.OIDC<br>(ISystemHashService consumer, interface-only)"]
    APP["Consuming application"]

    SYSHASH -.->|NuGet ref| SC
    SYSHASH -.->|NuGet ref: documented exception| HASH
    SE -.->|NuGet ref| SYSHASH
    ENVCLIENT -.->|interface only, no package ref| SC
    OIDC -.->|interface only, no package ref| SC
    APP -.->|NuGet ref| SE

Sequence diagram

The following diagram shows the primary salted-credential issue and validate round trip.

sequenceDiagram
    participant Caller
    participant SystemHashService
    participant SaltShaker

    Caller->>SystemHashService: Get(systemType)
    SystemHashService->>SystemHashService: GetAssembly() + SHA-256/Base64 hash
    SystemHashService->>SaltShaker: new SaltShaker(DateTime.Now)
    SystemHashService->>SaltShaker: SaltHash(systemHash)
    SaltShaker-->>SystemHashService: salted hash string
    SystemHashService-->>Caller: salted hash string

    Caller->>SystemHashService: RemoveSystemHashSaltAndValidateLifetime(saltedHash, expireSeconds, nowBufferSeconds)
    SystemHashService->>SaltShaker: new SaltShaker()
    SystemHashService->>SaltShaker: BlandHash(saltedHash, out createDate)
    SaltShaker-->>SystemHashService: original hash + createDate
    SystemHashService->>SystemHashService: Compare createDate against lifetime
    SystemHashService-->>Caller: value, or throw SystemHashExpiredException

Design constraints

  • Get(type) recomputes and re-salts on every call rather than caching, so each call normally returns a different string — though this is not guaranteed, since calls within the same millisecond share a timestamp and the randomized insertion choices can repeat — contradicting the class and interface XML-doc wording that implies caching*.
  • GetAssembly() returns Assembly.GetCallingAssembly(), not Assembly.GetEntryAssembly() as its doc comment states; with the parameterless constructor this most likely resolves to the SAIF.NetCore.SystemHash assembly itself rather than the consuming application, making the identity non-unique per application unless the caller supplies an assembly explicitly*.
  • No in-repo production code calls ComputeHashNow or RemoveSystemHashSaltAndValidateLifetime; both are intended for the external validating service (the Environment API's "GetSystemHash route")*.
  • Lifetime validation uses host-local DateTime.Now for both the embedded create date and the validation clock, so issuer and validator must share the same local time zone and a culture that round-trips HashUtil's date format for expiry checks to be correct*.
  • SAIF.NetCore.ServiceExtensions's RegisterEnvironmentClient (non-secure path) registers ISystemHashService via a factory that returns null, so any SystemType secure-setting overload invoked in a non-secure startup throws a NullReferenceException*.

Note: Statements marked with * were inferred from code structure, naming conventions, or contextual evidence where direct confirmation was not available in source artifacts.

Sources

  • src/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash.csproj
  • src/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SystemHashService.cs
  • src/SAIF.NetCore.HashUtil/SAIF.NetCore.HashUtil/SaltShaker.cs, SystemHashExpiredException.cs
  • src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/IServiceCollectionExtensions.cs
  • src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/BaseEnvironmentClient.cs
  • src/SAIF.NetCore.OIDC/src/SAIF.NetCore.OIDC/Services/OidcSettings.cs
  • Architecture.md