Skip to content

SAIF.NetCore.SystemHash security and authorization

This library implements no user or AD-group authorization. It is the shared client/validator toolkit for a machine/application (caller-identity) authorization scheme: an application presents a claimed identity via a salted, time-boxed credential derived from its own assembly name and a SystemType classification. The credential is not proof of possession — it is derivable by anyone who knows the target's assembly name and SystemType (see SEC-SH-F01) — and this library's own lifetime check (EP-SH-01) verifies only that the credential is fresh. The final accept/reject decision, including comparing the recovered identity against an authorized value, is made by an external validator (the Environment API / CryptoAPI), which is out of this repository's scope.

Authorization architecture

The following diagram traces the end-to-end flow across the three distinct roles: this library issues a salted credential and locally checks only its freshness, while the external validator (Environment API / CryptoAPI) performs the final identity comparison and accept/reject decision.

flowchart TD
    Assembly[Resolve system name from assembly] --> Identity["GetHashValue: SHA-256/Base64 of systemName-ordinal"]
    Identity --> Salt["SaltShaker.SaltHash with DateTime.Now"]
    Salt --> Credential["Salted credential, regenerated<br>and randomized per call"]
    Credential --> Transport["systemHashValue query parameter"]
    Transport --> Validator["(External: Environment API / CryptoAPI)"]
    Validator --> Desalt["RemoveSystemHashSaltAndValidateLifetime:<br>de-salt and check window"]
    Desalt --> Expired(["Reject: SystemHashExpiredException"])
    Desalt --> Compare["Compare returned hash to authorized/<br>ComputeHashNow value (external)"]
    Compare --> Accept([Accept])
    Compare --> Reject([Reject])

Identity catalog

This library's "resources" are system identities, not AD groups. An identity is the string "{systemName}-{(int)SystemType}"; its SHA-256/Base64 digest is the canonical identity hash (Compute the identity hash). No AD group, LDAP string, or SAMAccountName/$-suffix machine-account handling exists anywhere in this library.

Identity string (pre-hash) SystemType (ordinal) Produced by How systemName is resolved Source
"{Startup assembly simple name}-{n}" Caller-chosen SystemHashService(Assembly.GetAssembly(type)).Get(type) registered by RegisterSecureEnvironmentClient Injected assembly = assembly that contains the startup Type src/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SystemHashService.cs L19, L61–65; src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/IServiceCollectionExtensions.cs
"SAIF.NetCore.DBConnectionClient-2" WebApp (2) new SystemHashService(typeof(ConnectionClient).Assembly).Get(SystemType.WebApp) Injected assembly = ConnectionClient assembly (AssemblyName = SAIF.NetCore.DBConnectionClient per its csproj) src/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SystemHashService.cs L19, L61–65; src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/IServiceCollectionExtensions.cs
"SAIF.NetCore.OIDC-6" Other (6) _systemHashService.ComputeHash(SystemType.Other, Assembly.GetExecutingAssembly().GetName().Name) inside OidcSettings GetExecutingAssembly() is always the OIDC library assembly; the simple name shown here is inferred from the OIDC project's assembly name, not read directly from a build artifact.* src/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SystemHashService.cs L26–32; src/SAIF.NetCore.OIDC/src/SAIF.NetCore.OIDC/Services/OidcSettings.cs L32
"SAIF.NetCore.SystemHash-{n}" (probable) Any new SystemHashService().Get(type) (no injected assembly) Assembly.GetCallingAssembly() evaluated inside GetAssembly, which Get calls, so it most likely resolves to the SystemHash assembly itself (BR-SH-06), unless JIT inlining collapses the call stack.* src/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SystemHashService.cs L61–65, L72–75

SystemType ordinals (SAIF.NetCore.ServiceContracts): Console=0, WindowsService=1, WebApp=2, Guidewire=3, ThickClient=4, Script=5, Other=6.

Authorization enforcement points

Attribute-based authorization

None. The library has no attribute, controller, or action of any kind.

Programmatic authorization checks

ID Enforcement point Kind What it checks Outcome on failure Source
EP-SH-01 SystemHashService.RemoveSystemHashSaltAndValidateLifetime(systemHashValue, expireSeconds, nowBufferSeconds) Credential lifetime gate (validator side) De-salts the credential, recovers the embedded createDate, and accepts only if createDate ≤ now + abs(nowBufferSeconds) AND now − abs(nowBufferSeconds) < createDate + expireSeconds (BR-SH-08); this simplified predicate holds only for non-overflowing int arithmetic and in-range DateTime values — see BR-SH-08 for the literal source arithmetic and its failure modes throw new SystemHashExpiredException(now, createDate) (type from SAIF.NetCore.HashUtil); or an uncaught ArgumentOutOfRangeException from the date arithmetic itself src/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SystemHashService.cs L85–100

EP-SH-01 is documented step-by-step in De-salt and validate credential lifetime. There are no in-repo production callers of EP-SH-01 — only this library's own unit tests exercise it; the documented intended caller is the external Environment API / CryptoAPI.

Supporting (non-enforcing) credential operations

ID Member Role in scheme
SP-SH-01 ComputeHash(SystemType, string) Issues a credential: identity hash salted with DateTime.Now, regenerated and randomized on every call (not guaranteed unique). See Compute a salted system hash.
SP-SH-02 Get(SystemType) Issues a credential for the resolved assembly identity; no caching despite the XML doc. See Get the current application's system hash.
SP-SH-03 GetAssembly() Resolves the identity name. See Resolve the identity assembly.
SP-SH-04 ComputeHashNow(SystemType, string) Produces the canonical (unsalted) identity hash a validator compares against EP-SH-01 output. See Compute an unsalted system hash on demand.
SP-SH-05 GetHashValue (private) SHA-256 over UTF-16LE "{systemName}-{(int)type}", Base64, unkeyed. See Compute the identity hash.

Data-level authorization

None. This library performs no entity-ownership or scope checks; it issues and validates a caller-identity credential only.

Segregation of duties

None. No SOD rules exist in this library.

Security interfaces and contracts

Interface / Class Role Consumers
ISystemHashService (SAIF.NetCore.ServiceContracts) Credential issuance and lifetime-validation contract SystemHashService (sole implementation); BaseEnvironmentClient, OidcSettings (external consumers)
SystemHashExpiredException (SAIF.NetCore.HashUtil) Signals an expired or not-yet-valid credential SystemHashService.RemoveSystemHashSaltAndValidateLifetime

Machine/application authorization

This library implements application/machine-identity authorization by bearer credential, not the AD machine-account pattern — there is no SAMAccountName, $-suffix handling, or MachineSecurityService lookup anywhere in this repository.

Element Implementation
Identity basis SystemType + system name (assembly simple name or caller-supplied string)
Credential issuance ComputeHash/Get → SHA-256 identity hash, salted by SaltShaker(DateTime.Now) with an embedded timestamp
Credential transport systemHashValue query parameter (consumer call sites)
Lifetime validation RemoveSystemHashSaltAndValidateLifetime(value, expireSeconds, nowBufferSeconds) → SystemHashExpiredException
Canonical identity for comparison ComputeHashNow
Authorized-system list comparison Not in this library — unknown, external (Environment API / CryptoAPI)
Relationship to user authorization Independent; authorizes the calling application, not a person

Credential flow:

Issuer (consumer app)                                   Validator (external Environment API / CryptoAPI)
---------------------                                   ------------------------------------------------
GetAssembly() -> name ┐
                      ├─ GetHashValue: SHA256(UTF16("{name}-{ordinal}")) -> Base64 (44)
SystemType ───────────┘
SaltShaker(DateTime.Now).SaltHash -> salted credential (~120 chars, regenerated/randomized per call)
        │  systemHashValue query param
        └──────────────────────────────────────────────►  RemoveSystemHashSaltAndValidateLifetime(v, expire, buffer)
                                                            ├─ expired/not-yet-valid -> SystemHashExpiredException
                                                            └─ returns unsalted hash -> compare to authorized hash
                                                               (ComputeHashNow / stored list — not in this repo)

Security findings and risks

Severity scale: High (realistic path to acting with another system's authority), Medium (weakens a control or enables identity confusion under plausible conditions), Low (fails closed / limited impact), Informational.

# Finding Severity Source
SEC-SH-F01 Unkeyed, publicly derivable credential: the identity hash is SHA-256 of a public string (assembly name plus enum ordinal), with no secret, key, HMAC, or signature. Anyone who knows the target's assembly name and SystemType can compute the identity hash and, using the public SaltShaker, mint a credential that passes EP-SH-01. Scheme strength depends entirely on validator/network-level controls.* High src/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SystemHashService.cs
SEC-SH-F02 Identity collision when Get() runs without an injected assembly: GetAssembly() returns Assembly.GetCallingAssembly(), evaluated with Get as the caller, so the identity is probably "SAIF.NetCore.SystemHash-{n}" for every application that uses new SystemHashService() directly. JIT inlining could change this between Debug/Release. Production DI always injects an assembly, so exposure is limited to direct new SystemHashService() usage.* Medium src/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SystemHashService.cs
SEC-SH-F03 Shared identity via OIDC consumer: OidcSettings passes Assembly.GetExecutingAssembly().GetName().Name, which inside OidcSettings always resolves to the OIDC library assembly — every application using SAIF.NetCore.OIDC presents the same identity when retrieving app credentials. Per-application isolation then depends on the secure-setting lookup path rather than on the hash.* Medium src/SAIF.NetCore.OIDC/src/SAIF.NetCore.OIDC/Services/OidcSettings.cs
SEC-SH-F04 Null ISystemHashService registration in a sibling library: RegisterEnvironmentClient registers services.AddSingleton<ISystemHashService>((provider) => { return null; }) for non-secure startups, so BaseEnvironmentClient's SystemType overloads throw NullReferenceException instead of presenting a credential. Fails closed (no credential sent) but the failure is unhandled. Medium (availability) src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/IServiceCollectionExtensions.cs
SEC-SH-F05 Replayable or re-datable credential within the window: the credential has no nonce or audience, and the embedded timestamp sits at salt-map-disclosed positions. A captured credential can be replayed throughout its full expireSeconds + abs(nowBufferSeconds) lifetime from issuance — of which only the final abs(nowBufferSeconds) is grace past its nominal createDate + expireSeconds expiry — or re-salted with a newer time, defeating the lifetime limit (BR-SH-08). Exposure is higher because the credential travels in a query string, which is often logged.* Medium src/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SystemHashService.cs, src/SAIF.NetCore.HashUtil/SAIF.NetCore.HashUtil/SaltShaker.cs
SEC-SH-F06 Lifetime check depends on time zone and culture: issuer and validator both use host-local DateTime.Now, and HashUtil formats/parses the embedded date with the current culture. If hosts differ in time zone, credentials can be rejected incorrectly or accepted for too long.* Low–Medium src/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SystemHashService.cs
SEC-SH-F07 Malformed credentials are not normalized: tampered or garbage input produces BCL exceptions (ArgumentOutOfRangeException, FormatException, NullReferenceException) rather than SystemHashExpiredException (BR-SH-09). A validator that catches only SystemHashExpiredException could return a 500 or leak a stack trace instead of a clean authorization failure.* Low src/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SystemHashService.cs
SEC-SH-F08 Validation does not establish identity: EP-SH-01 checks freshness only (BR-SH-10). If a validator stops after EP-SH-01 and skips comparing the returned hash, any well-formed, fresh credential passes. Informational (design) src/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SystemHashService.cs
SEC-SH-F09 Identity depends on enum ordinals: reordering or inserting SystemType members in ServiceContracts silently changes every identity hash for later members, which would break authorization for already-registered systems (BR-SH-02).* Low src/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/Enums.cs

Notes

  • The external validator's expireSeconds/nowBufferSeconds values, the authorized-system comparison, and transport protections are not determinable from this repository.**

Note: Statements marked with * were inferred from code structure, naming conventions, or contextual evidence where direct confirmation was not available in source artifacts. Statements marked with ** could not be confirmed; treat as unknown until verified.

Sources

  • src/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SystemHashService.cs
  • src/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash.Tests/SystemHashTests.cs
  • src/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/ISystemHashService.cs, Enums.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