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/nowBufferSecondsvalues, the authorized-system comparison, and transport protections are not determinable from this repository.**
Related documentation¶
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.cssrc/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash.Tests/SystemHashTests.cssrc/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/ISystemHashService.cs,Enums.cssrc/SAIF.NetCore.HashUtil/SAIF.NetCore.HashUtil/SaltShaker.cs,SystemHashExpiredException.cssrc/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/IServiceCollectionExtensions.cssrc/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/BaseEnvironmentClient.cssrc/SAIF.NetCore.OIDC/src/SAIF.NetCore.OIDC/Services/OidcSettings.cs