Remaining items from the security review (Feb 2026). Items marked DONE have been addressed; the rest are listed by priority.
-
P0: Constant-time MAC comparison — replaced
Arrays.equalswithMessageDigest.isEqualinOpaqueAke,OpaqueEnvelope, andServer. -
P0: EC point validation —
OpaqueCrypto.deserializePointandOctetStringUtils.toEcPointnow reject identity and off-curve points. -
P1: Deserialization bounds checks —
KE2.deserializeandEnvelope.deserializevalidate input length beforeSystem.arraycopy. -
P1: Session store DoS protection —
OpaqueResource.pendingSessionsis capped at 10,000 entries with per-entry TTL instead of bulk clear. -
P1: Base64 decode error handling — all
B64D.decodecalls wrapped indecodeBase64()helper returning HTTP 400 for malformed input. -
Input validation — null/blank checks on all REST endpoint inputs; OPRF resource validates hex EC point input.
-
Unified error messages — all MAC/envelope failures throw generic "Authentication failed" to prevent protocol state leakage.
-
Implement key material cleanup —
ClientAuthStateandClientRegistrationStatenow implementAutoCloseable;close()zeros thepasswordbyte array viaArrays.fill.BigIntegerfields (blind,clientAkePrivateKey) are immutable and cannot be zeroed at the Java level. -
Shut down sessionReaper on app shutdown — Added
HofmannOpaqueServerManager.shutdown()which callssessionReaper.shutdown().HofmannBundleregisters a DropwizardManagedlifecycle component that calls it on stop.HofmannAutoConfigurationdeclares the bean with@Bean(destroyMethod = "shutdown")for Spring Boot. -
Add request size limits — Added
maxRequestBodyBytesfield toHofmannConfiguration(default 65536 bytes / 64 KiB).HofmannBundleregisters a JAX-RSContainerRequestFilterthat checksContent-Lengthand returns HTTP 413 if the header exceeds the configured limit. -
Constant-time modular inverse —
OprfCipherSuite.finalize()now uses Fermat inversionblind.modPow(n-2, n)instead ofBigInteger.modInverse(). -
Subgroup membership checks —
WeierstrassGroupSpecImpl.deserializePoint()documents the cofactor-1 assumption explicitly. A guarded runtime check (n·P = O) is included for defense-in-depth should a cofactor>1 curve be added. -
Protect credential deletion endpoint —
DELETE /opaque/registrationnow requires a valid JWT bearer token with subject matching the credential being deleted. -
Sanitize IAE messages in HTTP 400 responses — All catch blocks return a generic message instead of forwarding
e.getMessage(). Originals logged at DEBUG. -
Add status check in
HofmannOprfAccessor— Validates HTTP status code before deserializing: 401 throwsSecurityException, other 4xx/5xx throwsOprfAccessorException. -
Add dependency vulnerability scanning — OWASP Dependency-Check Gradle plugin, fails the build on CVSS >= 7.
-
Make
OpaqueCrypto.randomBytes()injectable —RandomProviderwith injectableSecureRandomviaOpaqueConfig. -
Builder pattern for cipher suites —
OprfCipherSuite.builder()provides a fluent builder withwithSuite(),withRandom(),withRandomProvider(). -
Add ristretto255-SHA512 cipher suite —
Ristretto255GroupSpec.javaimplementsGroupSpecwith pure BigInteger Edwards25519 arithmetic.RISTRETTO255_SHA512constant in bothOprfCipherSuiteandOpaqueCipherSuite. OPRF test vectors pass. -
Document CSRF disable rationale — Documented in
SECURITY.mdunder "CSRF Disabled in HofmannSecurityConfig" with full justification. -
Document TLS requirement — Documented in
SECURITY.mdunder "TLS Required for Production" with threat analysis and deployment patterns (reverse proxy, cloud LB, application-level). -
Document secrets management — Documented in
USAGE.mdunder "Injecting secrets from environment variables" with examples for Spring Boot, Dropwizard, Docker Compose, and Kubernetes. -
Constant-time scalar serialization — Extracted
ByteUtils.scalarToFixedBytes()that always routes through a zero-padded intermediate buffer, eliminating data-dependent branching onBigInteger.toByteArray()length. Replaced all four call sites:Server.java,HofmannBundle.java,HofmannAutoConfiguration.java,WeierstrassGroupSpecImpl.serializeScalar(). -
Add HTTP security headers — Spring Boot: added
.headers()DSL toHofmannSecurityConfigconfiguringX-Frame-Options: DENY,X-Content-Type-Options: nosniff,Strict-Transport-Security(1 year, includeSubDomains), andCache-Control: no-store. Dropwizard: addedSecurityHeadersFilter(JAX-RSContainerResponseFilter) registered inHofmannBundle.run()setting the same four headers. -
Add CORS configuration — Spring Boot: added explicit
.cors()DSL toHofmannSecurityConfigwith aCorsConfigurationSourcebean that blocks all cross-origin requests by default (empty allowed-origins list). Override thecorsConfigurationSourcebean to permit specific origins. Dropwizard: addedCorsFilter(JAX-RSContainerResponseFilter) registered inHofmannBundle, configured viacorsAllowedOriginsin YAML. Both restrict methods to GET/POST/DELETE and headers to Content-Type/Authorization. -
Restrict actuator health endpoint — Removed
/actuator/healthfrom thepermitAll()list inHofmannSecurityConfig; it now requires authentication like all other non-OPAQUE/OPRF endpoints. Removed public key length detail from bothOpaqueServerHealthIndicator(Spring Boot) andOpaqueServerHealthCheck(Dropwizard) health responses. -
Zero intermediate key material —
OpaqueAke.javanow explicitly zerosdh1,dh2,dh3,ikm,prk,handshakeSecret,km2,km3, andexpectedServerMacviaArrays.fill(..., (byte) 0)immediately after each value is consumed. On authentication failure ingenerateKE3, all remaining key material is zeroed before throwing.BigIntegerscalars remain immutable and cannot be zeroed at the Java level. -
Add rate limiting — Added
RateLimiterinterface withInMemoryRateLimiter(token-bucket) default implementation. OPAQUE endpoints rate-limit by credential identifier (/auth/start: 10 req/min,/registration/start: 5 req/min). OPRF endpoint rate-limits by client IP (30 req/min). All limits configurable; users can override with customRateLimiterimplementations (e.g. Redis-backed). Spring Boot:@ConditionalOnMissingBeanbeans with@Qualifier. Dropwizard: created inHofmannBundle.run()with managed shutdown lifecycle. -
TS: Replace custom
modPowwith noble'sinvert()— Removed the custom variable-timemodPow()function fromsuite.ts. Weierstrass suites now useinvert(blindScalar, ORDER)from@noble/curves/abstract/modular, which provides constant-time modular inversion. Ristretto255 suite uses the sameinvert(). -
TS: Constant-time
constantTimeEqual()for length mismatch — Replaced early-returnif (a.length !== b.length) return falsewith branch-freediff = len ^ b.lengththat XORs the length difference into the accumulator, then compares up toa.lengthusingb[i] ?? 0for out-of-bounds access. -
TS: Distinguish 401 from other HTTP errors — Added
OpaqueAuthenticationErrorclass (extendsError). Both_post()anddeleteRegistration()inOpaqueHttpClientnow throwOpaqueAuthenticationErrorfor HTTP 401 responses, matching the JavaHofmannOpaqueAccessorbehavior. Exported from package index. -
TS: Zero intermediate key material —
derive3DHKeys()inake.tsnow zerosdh1,dh2,dh3,ikm,prk, andhandshakeSecretvia.fill(0)immediately after use.generateKE3()inclient.tszeroskm2andkm3after computing MACs (including on auth failure). Matches JavaOpaqueAke.java. -
TS: Add cleanup utilities for client state — Added
zeroRegistrationState()andzeroAuthState()functions intypes.ts, exported from package index. These zero allUint8Arrayfields inClientRegistrationStateandClientAuthStaterespectively. Callers control when to invoke cleanup (matching Java'sAutoCloseablepattern).BigIntegerscalars remain immutable and cannot be zeroed at the JS level. -
Production KSF enforcement — Added
allowIdentityKsfproperty (defaultfalse) to bothHofmannProperties(Spring Boot) andHofmannConfiguration(Dropwizard). Whenargon2MemoryKib=0andallowIdentityKsfis nottrue, startup fails withIllegalStateExceptionexplaining how to opt in. Test configs updated withallow-identity-ksf: true/allowIdentityKsf: true. Unit test inIdentityKsfEnforcementTestverifies rejection, opt-in, and Argon2id-without-flag paths.
(All P1 items completed.)
(All P2 items completed.)
(All P3 items completed.)