Enterprise edition
FIPS 140 — Deep Reference
At a glance
Section titled “At a glance”This page is the deep reference for the NextPDF Enterprise FIPS 140 module. The module is a policy and self-test capability. It restricts cryptographic choices to a FIPS-aligned allow-list, runs a power-on known-answer-test battery, and inhibits cryptographic output when the battery fails. A FIPS-compatible deployment additionally requires a FIPS-validated cryptographic provider supplied by the operator.
Availability & licensing
Section titled “Availability & licensing”This capability ships in NextPDF Enterprise (nextpdf/enterprise) and activates with an Enterprise-tier license envelope. A deployment without that entitlement does not load the capability’s classes. Compare editions and get a license.
Public API surface
Section titled “Public API surface”All symbols live in NextPDF\Enterprise\Security\Fips, except FipsBootstrap in NextPDF\Enterprise\Bootstrap.
| Symbol | Parameters | Default behavior | Returns | Throws or fails with | Notes |
|---|---|---|---|---|---|
FipsBootstrap::boot() | ?CryptoPolicyInterface $policy, ?FipsSelfTest $selfTest, ?LoggerInterface $auditLogger (all default null) | Runs the power-on battery once at start-up; defaults to the strict policy | FipsModeGuard | FipsModuleErrorStateException on any power-on test failure | Composition root; audit trail off when $auditLogger is null |
FipsBootstrap::lazy() | Same as boot() | Defers the battery to the first boundary assertion | FipsModeGuard | None at call time; the first assertion can throw FipsModuleErrorStateException | Module stays PRE_OPERATIONAL until first assertion |
FipsBootstrap::signatureEnforcer() | ?CryptoPolicyInterface $policy = null, ?FipsSelfTest $selfTest = null | Boots the module, then wraps the guard for signer chokepoints | FipsSignatureEnforcer | FipsModuleErrorStateException on any power-on test failure | Delegates to boot() |
FipsBootstrap::selfTestReport() | ?FipsSelfTest $selfTest = null | Runs the battery on demand and summarizes it | array{status: FipsSelfTestStatus, operational: bool, failed: list<string>} | Does not throw; failures appear in failed | For admin health endpoints and CLI use |
FipsCryptoPolicy::strict() | ?FipsSelfTest $selfTest = null | FIPS 140-3 preset: SHA-256/384/512; approved RSA, RSASSA-PSS, ECDSA OIDs; aes-256-cbc, aes-256-gcm; RSA >= 2048, EC >= 256 | self | None | Constructor is private; presets are the only entry |
FipsCryptoPolicy::standard() | ?FipsSelfTest $selfTest = null | FIPS 140-2 preset: strict set plus aes-128-cbc | self | None | Legacy interoperability only |
FipsCryptoPolicy::assertPreOperational() | None | Runs, or replays the latched result of, the power-on battery | void | FipsModuleErrorStateException when any power-on test fails | Driven by the Core enforcement seam before the first operation |
FipsCryptoPolicy query surface | string / int inputs | Allow-list membership checks; unknown key type is denied | bool / string | None | isHashAlgorithmAllowed, isSignatureAlgorithmAllowed, isEncryptionAlgorithmAllowed, isKeyStrengthAllowed, getPreferredHashAlgorithm, getName |
FipsModeGuard::__construct() | CryptoPolicyInterface $policy, ?FipsBootGuard $bootGuard = null, ?FipsAuditLogger $auditLogger = null | Wraps a policy with assert-style boundaries | — | None | Without $bootGuard there is no self-test gate; production composition supplies it |
FipsModeGuard::assertHashAllowed() | string $algorithm | Deny catalogue first, then the allow-list | void | FipsViolationException; FipsModuleErrorStateException when a boot guard is wired | Audit record precedes any FipsViolationException |
FipsModeGuard::assertSignatureAlgorithmAllowed() | string $oid | Deny catalogue first, then the allow-list | void | Same as above | OIDs are matched exactly |
FipsModeGuard::assertEncryptionAllowed() | string $algorithm | Deny catalogue first, then the allow-list | void | Same as above | Names are compared lowercase |
FipsModeGuard::assertKeyStrengthAllowed() | string $keyType, int $bitLength | Deny catalogue first, then the policy minimums | void | Same as above | Unknown key type is denied |
FipsModeGuard::getPolicy() | None | Returns the wrapped policy | CryptoPolicyInterface | None | — |
FipsBootGuard::__construct() | FipsSelfTest $selfTest | Holds the battery; does not run it | — | None | One power-on cycle per instance |
FipsBootGuard::report() | None | Runs the battery once, caches the report, latches on error | FipsSelfTestReport | None | First call runs the tests |
FipsBootGuard::rerun() | None | Forces a fresh run; an error run latches the process | FipsSelfTestReport | None | On-demand self-test; not an error-recovery path |
FipsBootGuard::assertOperational() | None | Asserts the module is operational; sticky across the process | void | FipsModuleErrorStateException | A clean instance still throws when the process latched an error |
FipsBootGuard::status() | None | Reports the cached status | FipsSelfTestStatus | None | ERROR when latched; PRE_OPERATIONAL when never run |
FipsSelfTest::__construct() | ?callable $randomBytesProvider = null, ?callable $hashProvider = null | Uses platform hash() and random_bytes() | — | None | Overrides exist for deterministic failure tests |
FipsSelfTest::run() | None | Executes the full battery; never short-circuits | FipsSelfTestReport | None | Failures land in the report, not in exceptions |
FipsSelfTestReport | FipsSelfTestStatus $status, array $results, array $failedResults | Immutable aggregate of one run | — | assertOperational() throws FipsModuleErrorStateException in the error state | Also isOperational(), isError() |
FipsSelfTestReport::assertOperational() | None | Returns unless the report status is ERROR | void | FipsModuleErrorStateException carrying failedResults | The canonical start-up abort for production code (ISO/IEC 19790 §7.10 error state) |
FipsSelfTestResult | string $algorithm, string $kind, bool $passed, string $message = '' | Immutable per-test outcome | — | None | kind is KAT, PWCT, or HEALTH; also isPassed(), isFailed() |
FipsSelfTestStatus | — | String-backed enum | — | None | Cases PRE_OPERATIONAL, OPERATIONAL, ERROR |
FipsSignatureEnforcer::__construct() | FipsModeGuard $guard | Wraps a boot-gated guard for signing chokepoints | — | None | — |
FipsSignatureEnforcer::assertSignatureGenerationAllowed() | string $algorithm, string $certificatePem | Resolves the OID and key strength, then delegates to the guard | void | FipsViolationException (unknown algorithm, unapproved PSS digest, unprovable key, or policy denial); FipsModuleErrorStateException via the guard | Generation path only; verification never routes here |
FipsAuditLogger::__construct() | CryptoPolicyInterface $policy, LoggerInterface $logger | Wraps a PSR-3 logger and the same policy the guard enforces | — | None | Recorded decisions cannot diverge from enforced ones |
FipsAuditLogger::logHashOperation() / logSignatureOperation() / logEncryptionOperation() / logKeyStrengthCheck() | string / int inputs per decision | Logs ALLOW at INFO and DENY at WARNING | bool (true when allowed) | None | Structured context: policy name, item, decision |
FipsTransitioningAlgorithms | string / int inputs | Static SP 800-131A Rev.2 deny catalogue | bool / list<string> | None | isHashDisallowed, isSignatureOidDisallowed, isEncryptionDisallowed, isKeyStrengthDisallowed, plus disallowedHashes, disallowedSignatureOids, disallowedEncryption |
FipsTransitioningAlgorithms::isHashDisallowed() | string $algorithm | Lowercases the name, then tests membership in the disallowed-hash catalogue | bool | None | static; true for MD2, MD4, MD5, SHA-1, and RIPEMD-160 per SP 800-131A Rev.2 |
FipsViolationException | string $policyName, string $violatingItem, string $reason | Typed policy violation with public readonly fields | — | — | Subtype of NextPDF\Exception\NextPdfException |
FipsModuleErrorStateException | array $failedResults, ?string $message = null | Error-state refusal carrying the failed test results | — | — | Subtype of NextPDF\Exception\NextPdfException |
public static function boot(?CryptoPolicyInterface $policy = null, ?FipsSelfTest $selfTest = null, ?LoggerInterface $auditLogger = null): FipsModeGuardpublic static function lazy(?CryptoPolicyInterface $policy = null, ?FipsSelfTest $selfTest = null, ?LoggerInterface $auditLogger = null): FipsModeGuardpublic static function signatureEnforcer(?CryptoPolicyInterface $policy = null, ?FipsSelfTest $selfTest = null): FipsSignatureEnforcerpublic static function selfTestReport(?FipsSelfTest $selfTest = null): arraypublic static function strict(?FipsSelfTest $selfTest = null): selfpublic static function standard(?FipsSelfTest $selfTest = null): selfpublic function assertPreOperational(): voidpublic function isHashAlgorithmAllowed(string $algorithm): boolpublic function isSignatureAlgorithmAllowed(string $oid): boolpublic function isEncryptionAlgorithmAllowed(string $algorithm): boolpublic function isKeyStrengthAllowed(string $keyType, int $bitLength): boolpublic function getPreferredHashAlgorithm(): stringpublic function getName(): stringpublic function __construct(private CryptoPolicyInterface $policy, private ?FipsBootGuard $bootGuard = null, private ?FipsAuditLogger $auditLogger = null)public function assertHashAllowed(string $algorithm): voidpublic function assertSignatureAlgorithmAllowed(string $oid): voidpublic function assertEncryptionAllowed(string $algorithm): voidpublic function assertKeyStrengthAllowed(string $keyType, int $bitLength): voidpublic function getPolicy(): CryptoPolicyInterfacepublic function __construct(private readonly FipsSelfTest $selfTest)public function report(): FipsSelfTestReportpublic function rerun(): FipsSelfTestReportpublic function assertOperational(): voidpublic function status(): FipsSelfTestStatuspublic function __construct(?callable $randomBytesProvider = null, ?callable $hashProvider = null)public function run(): FipsSelfTestReportpublic function __construct(private readonly FipsModeGuard $guard)public function assertSignatureGenerationAllowed(string $algorithm, string $certificatePem): voidpublic function __construct(private CryptoPolicyInterface $policy, private LoggerInterface $logger)public function logHashOperation(string $algorithm): boolpublic function logSignatureOperation(string $oid): boolpublic function logEncryptionOperation(string $algorithm): boolpublic function logKeyStrengthCheck(string $keyType, int $bitLength): boolpublic static function isHashDisallowed(string $algorithm): boolpublic static function isSignatureOidDisallowed(string $oid): boolpublic static function isEncryptionDisallowed(string $algorithm): boolpublic static function isKeyStrengthDisallowed(string $keyType, int $bitLength): boolpublic static function disallowedHashes(): arraypublic static function disallowedSignatureOids(): arraypublic static function disallowedEncryption(): arraypublic function __construct(public FipsSelfTestStatus $status, public array $results, public array $failedResults)public function isOperational(): boolpublic function isError(): boolpublic function assertOperational(): void
// FipsSelfTestResultpublic function __construct(public string $algorithm, public string $kind, public bool $passed, public string $message = '')public function isPassed(): boolpublic function isFailed(): bool
// FipsSelfTestStatusenum FipsSelfTestStatus: string{ case PRE_OPERATIONAL = 'pre_operational'; case OPERATIONAL = 'operational'; case ERROR = 'error';}Behavior contract
Section titled “Behavior contract”- The policy allow-list is the authoritative decision.
FipsTransitioningAlgorithmsadds an explicit SP 800-131A Rev.2 deny layer above it, for audit-clear rejection messages. The deny layer never widens or overrides the allow-list. FipsCryptoPolicy::strict()permits SHA-256, SHA-384, and SHA-512; RSA PKCS#1 v1.5,RSASSA-PSS, and ECDSA signature OIDs bound to those hashes;aes-256-cbcandaes-256-gcm; and key floors of RSA 2048, EC 256, Ed25519 256.standard()additionally permitsaes-128-cbc.- The power-on battery covers: SHA-256/384/512 digest KATs, an HMAC-SHA-256 KAT, an AES-256-CBC encrypt-and-decrypt KAT, an AES-256-GCM tag KAT, an ECDSA P-256 pair-wise consistency test, and a DRBG health check.
FipsSelfTest::run()always executes every test and never short-circuits, so the report is complete for audit evidence. - The DRBG row is a continuous health test (draw length plus distinct successive draws), not a known-answer test. The DRBG known-answer obligation is delegated to the underlying FIPS-validated provider the operator supplies.
- The error state is process-sticky. The first
ERRORreport observed by anyFipsBootGuardlatches the whole process. A fresh guard or policy instance cannot launder the error, and a later passing rerun does not clear it. Only a process restart (a true power cycle) resets the state. FipsCryptoPolicyimplementsNextPDF\Contracts\CryptoPolicyInterfaceandNextPDF\Contracts\PreOperationalSelfTestInterface. When configured as the Core crypto policy, the Core enforcement seam drivesassertPreOperational()before the first signature or ciphertext is produced.FipsSignatureEnforcergates the generation path only. Verification of already-generated signatures is legacy use under SP 800-131A Rev.2 and never routes through the enforcer.- When an audit logger is wired, every assert boundary emits one ALLOW (INFO) or DENY (WARNING) record before any policy-violation throw. Every
FipsViolationExceptiondenial is therefore evidenced in the trail. The boot-guard gate runs first, so an error-state refusal is raised before the audit record. TheFipsAuditLoggerconsults the same policy the guard enforces, so recorded decisions cannot diverge from enforced ones.
Edge cases & failure modes
Section titled “Edge cases & failure modes”- An unknown key type is denied by both layers: the policy returns
falseand the catalogue treats it as disallowed. - A signing-algorithm identifier the enforcer cannot map to a signature OID is refused fail-closed with
FipsViolationException. - Every
RSASSA-PSSvariant shares the OID1.2.840.113549.1.1.10, so the OID alone cannot prove the digest. The enforcer binds the effective digest explicitly and denies any PSS token whose digest is not SHA-256/384/512. - A certificate that cannot be parsed, or whose public-key bit length is unavailable, is denied as
key:unprovable. - On a runtime without OpenSSL asymmetric primitives, the ECDSA pair-wise consistency test records a failure, not a skip, and the module enters
ERROR. - Two identical successive 32-byte random draws fail the DRBG health check (stuck-output detection) and force
ERROR. - A
FipsModeGuardconstructed without a boot guard performs policy checks only and has no self-test gate. Production composition goes throughFipsBootstrap, which always wires the gate. - Hash and cipher names are compared lowercase; signature OIDs are matched exactly, with no normalization.
- After
FipsBootstrap::lazy(), the module staysPRE_OPERATIONALuntil the first assert boundary runs the battery.PRE_OPERATIONALis treated as not operational at assertion time.
FIPS-mode behavior
Section titled “FIPS-mode behavior”This module is the FIPS-mode surface itself. While the module is in the error state, and while pre-operational self-tests run, cryptographic output is inhibited: every assert boundary throws FipsModuleErrorStateException before any signature or ciphertext is produced (ISO/IEC 19790:2025 §7.3.3 b), AS03.07). FipsBootGuard::status() and FipsBootstrap::selfTestReport() expose the state so an operator can determine that the module entered the error state (ISO/IEC 19790:2025 §7.10.3). These behaviors are capability claims about NextPDF code: the module boundary that FIPS 140 validates is the operator-supplied cryptographic provider.
Conformance
Section titled “Conformance”| Claim | Standard | Clause |
|---|---|---|
| FIPS 140-3 is based on ISO/IEC 19790 and ISO/IEC 24759; this page therefore cites ISO/IEC 19790 clauses. | FIPS 140-3 | Introduction (fips_140_3#x26.x2) |
| Output is inhibited in the error state and during pre-operational self-tests. | ISO/IEC 19790:2025 | §7.3.3 b) [AS03.07] |
| A known-answer test compares a computed result against a known expected output; the battery implements this shape. | ISO/IEC 19790:2025 | §7.10.4 |
| The operator can determine the error state through a status output. | ISO/IEC 19790:2025 | §7.10.3 [AS10.10] |
Operators can initiate the self-tests on demand for periodic testing; rerun() and selfTestReport() provide this. | ISO/IEC 19790:2025 | §7.10.5 [AS10.54] |
| SHA-1 is disallowed for new digital-signature generation; the deny catalogue rejects it. | NIST SP 800-131A Rev.2 | §9 |
| Signature generation below 112-bit strength (RSA < 2048, ECDSA order < 224) is disallowed; the key floors enforce this. | NIST SP 800-131A Rev.2 | §3 Table 2 |
| Verification of already-generated SHA-1 signatures is legacy use; it does not route through the generation gate. | NIST SP 800-131A Rev.2 | Change summary (9.x4.p12) |
All clauses are paraphrased; no normative text is reproduced. The module aligns its behavior with the cited clauses as a compliance-assistance capability. Whether a deployment is FIPS-compliant depends on the operator’s validated provider, module boundary definition, and compliance program.
Development notes
Section titled “Development notes”- Deterministic failure tests inject broken providers through the
FipsSelfTestconstructor, or through the$selfTestparameters onFipsCryptoPolicy::strict(),standard(), and theFipsBootstrapmethods. - The process-sticky error latch has an internal, test-only reset hook. It is not part of the supported API, and production code must not call it.
- Each PHP worker process runs its own power-on battery. The report is cached per instance, so hot-path assertions are constant-time status checks.
- Do not catch
FipsModuleErrorStateExceptionand continue. The exception means the module refuses cryptographic services; the correct response is to stop and restart the process after remediation. FipsBootstrap::selfTestReport()serves on-demand and periodic self-test needs, such as health endpoints. A passing on-demand run never clears a latched error.
See also
Section titled “See also”- FIPS 140-2/3 cryptographic policy and self-test — the capability page with setup and examples.
- Security — Deep Reference — the combined Enterprise security controls.
- Signature — Deep Reference — the PAdES long-term producer and its FIPS-mode note.
- Security — NextPDF Core — the Core encryption and signing surface the policy binds to.
Publication boundary
Section titled “Publication boundary”This page documents externally observable behavior and the supported public API surface only. Internal namespace paths, helper classes, mechanism tables, runbook filenames, and ticket prefixes are out of scope.