Pro edition
Cloud KMS signing — Deep Reference
At a glance
Section titled “At a glance”This page is the contract-level reference for the NextPDF Pro cloud-KMS signing surface. The surface consists of one Service Provider Interface, NextPDF\Pro\Security\Signing\Kms\KmsSignerInterface, and three provider signers: AwsKmsSigner, AzureKeyVaultSigner, and GcpKmsSigner. Two adapters, AwsKmsSigningStrategy and AzureKeyVaultSigningStrategy, bridge a signer to the Pro SigningStrategy contract. Each signer sends only a message digest to its provider over PSR-18 HTTP. The private key and the document never cross the boundary. This page states the public API, the observable behavior contract, and the typed failure modes. Session orchestration (RemoteSigningSession, SequentialSigner) and timestamping (PadesBtTimestamper) live on their own pages.
Availability & licensing
Section titled “Availability & licensing”This capability ships in NextPDF Pro (nextpdf/pro) and activates with a Pro-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”| Symbol | Parameters | Default behavior | Returns | Throws or fails with | Notes |
|---|---|---|---|---|---|
KmsSignerInterface | — | Extends the Core HsmSignerInterface contract | — | — | SPI for KMS and HSM drivers; reserved built-in ids: aws-kms, azure-keyvault, gcp-kms, pkcs11, openssl-cli |
KmsSignerInterface::providerId() | none | Stable registry lookup key | non-empty-string | — | Third-party drivers must namespace their identifier |
KmsSignerInterface::signWithVersion() | $data, $algorithm = 'sha256WithRSAEncryption', $keyVersion = null | null key version falls back to the provider default | string signature octets: RSA as returned by the provider (placed directly in SignerInfo.signature), ECDSA as DER ECDSA-Sig-Value per CMS rules | KeyManagementException, UnsupportedAlgorithmException, SignatureFailedException | Per-provider null semantics differ; see the behavior contract |
KmsSignerInterface::supportsAlgorithm() | string $algorithm | Capability probe; performs no I/O | bool | — | Called before provider selection |
KmsSignerInterface::supportedAlgorithms() | none | Lists the OpenSSL-style names the provider accepts | list<non-empty-string> | — | — |
AwsKmsSigner | constructor: AwsKmsConfig, cert DER, chain DER, PSR-18 client, PSR-17 factories, PSR-3 logger | Algorithm defaults to KmsSigningAlgorithm::RsaPkcs1Sha256 | — | see methods | final; PROVIDER_ID = 'aws-kms' |
AwsKmsSigner::create() | key id, cert DER, PSR dependencies, optional chain, config, logger | Builds AwsKmsConfig::fromEnvironment($keyId) when $config is null | self | — | Reads the standard AWS_* environment variables |
AwsKmsSigner::withAlgorithm() | KmsSigningAlgorithm $algorithm | Returns a modified clone | self | — | Must match the key type provisioned in AWS KMS |
AwsKmsSigner::sign() | $data, $algorithm = 'sha256WithRSAEncryption' | Delegates to signWithVersion($data, $algorithm, null) | string | as signWithVersion() | Legacy two-argument Core contract path |
AzureKeyVaultSigner | constructor: AzureKeyVaultConfig, cert DER, chain DER, PSR-18 client, PSR-17 factories, PSR-3 logger | Algorithm defaults to AzureSigningAlgorithm::Rs256; a config access token seeds the bearer token | — | see methods | final; PROVIDER_ID = 'azure-keyvault' |
AzureKeyVaultSigner::create() | vault name, key name, cert DER, PSR dependencies, optional chain, config, logger | Builds AzureKeyVaultConfig::fromEnvironment() when $config is null | self | — | Supports pre-obtained token or service-principal credentials |
AzureKeyVaultSigner::withAlgorithm() | AzureSigningAlgorithm $algorithm | Returns a modified clone | self | — | RSA keys use RS/PS values; EC keys use ES values |
GcpKmsSigner | constructor: GcpKmsConfig, cert DER, chain DER, PSR-18 client, PSR-17 factories, PSR-3 logger | Algorithm defaults to GcpKmsSigningAlgorithm::RsaSignPkcs1_2048Sha256 | — | see methods | final; PROVIDER_ID = 'gcp-kms', API_VERSION = 'v1' |
GcpKmsSigner::create() | project id, location, key ring, crypto key, cert DER, PSR dependencies, optional chain, config, logger | Builds GcpKmsConfig::fromEnvironment() when $config is null | self | — | Bearer-token acquisition is delegated to the caller |
GcpKmsSigner::withAlgorithm() | GcpKmsSigningAlgorithm $algorithm | Config-time preview only; the per-call wire name wins at sign time | self | — | Key size is fixed by the provisioned CryptoKeyVersion |
AwsKmsSigningStrategy::__construct | AwsKmsSigner $signer | Synchronous; isAsync() returns false | — | Propagates the wrapped signer’s exceptions | Adapter for RemoteSigningSession::complete() |
AzureKeyVaultSigningStrategy::__construct | AzureKeyVaultSigner $signer | Synchronous; isAsync() returns false | — | Propagates the wrapped signer’s exceptions | Adapter for RemoteSigningSession::complete() |
KmsSigningAlgorithm | enum, 9 cases (RSA PKCS#1, RSA-PSS, ECDSA; SHA-256/384/512) | — | AWS KMS SigningAlgorithm wire values | InvalidArgumentException from fromOpenSslName() | resolveForWireName() preserves the configured PSS digest |
AzureSigningAlgorithm | enum, 9 cases (RS256…ES512) | — | Azure Key Vault JWA-style values | InvalidArgumentException from fromOpenSslName() | isEcdsa() marks values whose output needs DER conversion |
GcpKmsSigningAlgorithm | enum, 10 cases (EC P-256/P-384, RSA PKCS#1, RSA-PSS) | — | GCP CryptoKeyVersion algorithm values | UnsupportedAlgorithmException from fromOpenSslName() | Wire-name resolution picks the smallest matching key size |
Entry-point signatures
Section titled “Entry-point signatures”public function providerId(): string;
public function signWithVersion( string $data, string $algorithm = 'sha256WithRSAEncryption', ?string $keyVersion = null,): string;
public function supportsAlgorithm(string $algorithm): bool;
public function supportedAlgorithms(): array;public static function create( string $keyId, string $certDer, ClientInterface $httpClient, RequestFactoryInterface $requestFactory, StreamFactoryInterface $streamFactory, array $chainDer = [], ?AwsKmsConfig $config = null, ?LoggerInterface $logger = null,): self
public function withAlgorithm(KmsSigningAlgorithm $algorithm): self
public function sign(string $data, string $algorithm = 'sha256WithRSAEncryption'): stringpublic static function create( string $vaultName, string $keyName, string $certDer, ClientInterface $httpClient, RequestFactoryInterface $requestFactory, StreamFactoryInterface $streamFactory, array $chainDer = [], ?AzureKeyVaultConfig $config = null, ?LoggerInterface $logger = null,): self
public function withAlgorithm(AzureSigningAlgorithm $algorithm): selfpublic static function create( string $projectId, string $location, string $keyRing, string $cryptoKey, string $certDer, ClientInterface $httpClient, RequestFactoryInterface $requestFactory, StreamFactoryInterface $streamFactory, array $chainDer = [], ?GcpKmsConfig $config = null, ?LoggerInterface $logger = null,): self
public function withAlgorithm(GcpKmsSigningAlgorithm $algorithm): selfpublic function __construct( private AwsKmsSigner $signer,) {}
public function sign(string $signedAttributesDer): stringpublic function __construct( private AzureKeyVaultSigner $signer,) {}
public function sign(string $signedAttributesDer): stringBehavior contract
Section titled “Behavior contract”Contract resolution
Section titled “Contract resolution”KmsSignerInterface extends the Core HsmSignerInterface contract. It adds providerId(), the key-version-aware signWithVersion(), and the supportsAlgorithm() and supportedAlgorithms() capability probes. The inherited two-argument sign() delegates to signWithVersion() with a null key version on all three signers. getCertificateDer(), getCertificateChainDer(), and getPublicKeyAlgorithm() are implemented from the constructor-supplied material. Capability probes perform no I/O. Each signer also exposes getSigningAlgorithm() and getConfig() accessors for inspection.
Digest-only transmission
Section titled “Digest-only transmission”Each signer hashes $data locally with the resolved algorithm’s digest and transmits only that digest. AWS receives a base64 digest with MessageType: DIGEST. Azure receives a base64url digest in the sign request body. GCP receives a base64 digest in the algorithm-specific digest field. The document bytes never appear in a provider request. All transport uses a standard PSR-18 HTTP client over the provider’s HTTPS endpoint; no cloud vendor SDK is involved.
Key-version resolution
Section titled “Key-version resolution”signWithVersion() validates the key-version argument fail-closed before any request is built. A value that fails the provider grammar raises KeyManagementException and prevents URL-segment or KeyId injection.
| Provider | null key version | Empty string | Override grammar |
|---|---|---|---|
AwsKmsSigner | Uses AwsKmsConfig::$keyId; an alias or ARN resolves to the current key on the provider side | Rejected | UUID (dashed or undashed), alias/<name>, or a KMS key/alias ARN |
AzureKeyVaultSigner | Uses the configured key version; an empty config value selects the latest enabled version server-side | Rejected | 32-character hexadecimal identifier |
GcpKmsSigner | Uses the version pinned in GcpKmsConfig; with none pinned, raises KeyManagementException | Rejected | Decimal CryptoKeyVersion id, digits only |
GCP has no server-side “active version” primitive. The asymmetric-sign endpoint operates on a specific cryptoKeyVersions/{n} resource only, so a version must always be resolvable.
Algorithm resolution
Section titled “Algorithm resolution”The strategy layer forwards an OpenSSL-style wire name. AWS and Azure accept seven wire names (PKCS#1 and ECDSA at SHA-256/384/512, plus RSASSA-PSS). GCP accepts five (sha256WithRSAEncryption, sha512WithRSAEncryption, RSASSA-PSS, ecdsa-with-SHA256, ecdsa-with-SHA384). The RSASSA-PSS wire name does not encode a digest, so it is digest-ambiguous. AwsKmsSigner resolves it through KmsSigningAlgorithm::resolveForWireName(), which preserves the digest of the configured PSS variant. AzureKeyVaultSigner trusts the configured PSS variant for the ambiguous name. It raises UnsupportedAlgorithmException if a resolved PSS digest would diverge from the configured one. GcpKmsSigner re-resolves the enum from the wire name on every call; withAlgorithm() on GCP is a config-time preview and does not change sign-time behavior. An unsupported wire name raises UnsupportedAlgorithmException before any network call. On AwsKmsSigner and GcpKmsSigner, a sign call updates the value later reported by getSigningAlgorithm() to the resolved per-call algorithm. On AzureKeyVaultSigner, resolution is call-local and the configured value stays authoritative.
Signature normalization
Section titled “Signature normalization”AWS and GCP return signatures in the form CMS consumes: RSA signature octets go into SignerInfo.signature unchanged, and ECDSA arrives DER-encoded. Azure returns ECDSA in raw IEEE P1363 (r||s) form, which the signer converts to a DER ECDSA-Sig-Value before returning.
CMS integration and adjacency
Section titled “CMS integration and adjacency”A SigningStrategy adapter signs the DER-encoded signed attributes supplied by the session. With signed attributes present, the CMS signature input is the digest of the complete DER encoding of the SignedAttrs value — RFC 5652 §5.4. The adapter’s getSignatureAlgorithmOid() and getDigestAlgorithm() feed the SignerInfo signatureAlgorithm and digestAlgorithm fields — RFC 5652 §5.3. The returned bytes become the SignerInfo signature OCTET STRING — RFC 5652 §5.5. CMS assembly, ByteRange handling, and session lifecycle belong to RemoteSigningSession; multi-party flows belong to SequentialSigner. A PAdES B-T signature-time-stamp, whose messageImprint hashes the SignerInfo signature value — RFC 3161 Appendix A — is applied by PadesBtTimestamper, not by these signers. All three are documented on the Pro security deep reference.
Edge cases & failure modes
Section titled “Edge cases & failure modes”- An empty-string key version is rejected on all three providers. Pass
nullto inherit the configured default. - A malformed key version is rejected before any request is built, with the offending value named in the exception.
AwsKmsSignerwith an emptyAwsKmsConfig::$keyIdand anullkey version raisesKeyManagementException.- Provider responses that indicate a key-management failure map to
KeyManagementException: AWSNotFoundException,DisabledException,KeyUnavailableException,InvalidKeyUsageException, or HTTP 404; Azure HTTP 404,KeyNotFound,KeyDisabled, orKeyNotActive; GCP HTTP 404 or 409,NOT_FOUND,FAILED_PRECONDITION, or an HTTP 400 whose message names a version. - Other non-200 provider responses raise
SignatureFailedExceptionon AWS and GCP, andAzureKeyVaultExceptionon Azure. - A PSR-18 transport failure during signing maps to
SignatureFailedExceptionwith the client exception preserved as the previous throwable. AzureKeyVaultSignerwith no access token and no service-principal credentials raisesAzureKeyVaultExceptionbefore any vault call. A failed Azure AD token acquisition also raisesAzureKeyVaultException.AzureKeyVaultSignervalidates the vault name, key name, key version, and tenant id against Azure’s published grammars at the request chokepoint. A value carrying URL-structural characters fails closed withAzureKeyVaultException.GcpKmsSignerwith no OAuth2 bearer token raisesSignatureFailedException; token acquisition is the caller’s responsibility.- A provider response that is not valid JSON, or that lacks the signature field, raises
SignatureFailedException(Azure: a missingvaluefield raisesAzureKeyVaultException). - A provider signature field that fails base64 decoding raises
SignatureFailedExceptionon AWS and GCP, andAzureKeyVaultExceptionon Azure. - No
SigningStrategyadapter forGcpKmsSignerships in 3.1.0. The GCP signer is consumed through theKmsSignerInterfacecontract directly.
FIPS-mode behavior
Section titled “FIPS-mode behavior”AwsKmsConfig::withFipsEndpoint() routes requests to the region’s kms-fips endpoint. The FIPS validation status of that endpoint is a property of AWS, not of NextPDF. AzureKeyVaultConfig and GcpKmsConfig expose no dedicated FIPS endpoint helper in 3.1.0. Digest computation runs in-process with the PHP hash() function and is not itself a validated module. NextPDF Pro can operate against a FIPS-validated KMS or HSM boundary.
Conformance
Section titled “Conformance”| Claim | Standard | Clause |
|---|---|---|
| The strategy signs the DER-encoded signed attributes; the CMS signature input digest covers the complete DER encoding of SignedAttrs. | RFC 5652 | §5.4 |
| SignedAttributes are DER-encoded and carry content-type and message-digest at minimum; signatureAlgorithm identifies the signer’s algorithm. | RFC 5652 | §5.3 |
| The returned signature bytes are encoded as an OCTET STRING and carried in the SignerInfo signature field. | RFC 5652 | §5.5 |
| A signature time-stamp’s messageImprint hashes the SignerInfo signature value (adjacent B-T surface, not these signers). | RFC 3161 | Appendix A |
All clauses are paraphrased; NextPDF does not reproduce normative text. Whether a produced signature verifies is the verifier’s decision against its own trust anchors and policy; the signers return signature bytes and assert no trusted outcome. Key custody, key protection, and provider-side algorithm validation are properties of the configured KMS, not of NextPDF.
Development notes
Section titled “Development notes”- Availability within the Pro package:
AwsKmsSignersince 1.9.0,AzureKeyVaultSignersince 2.0.0,GcpKmsSignerandKmsSignerInterfacesince 2.1.0. All are current innextpdf/pro3.1.0. - The signers depend only on PSR-18, PSR-17, and PSR-3. No AWS, Azure, or Google SDK is required or bundled.
- Probe
supportsAlgorithm()before signing so an incompatible provider is rejected at selection time, not mid-session. - Credential fields are constructor-injected and marked as sensitive parameters. Log messages carry structural fields only; no credential, token, or document content is written to logs.
- Pin key versions explicitly in regulated deployments. Alias-resolution (AWS) and latest-enabled (Azure) defaults are convenient but not deterministic across rotations.
- Third-party drivers implement
KmsSignerInterfaceand must namespace theirproviderId()to avoid collisions with the reserved built-in identifiers.
See also
Section titled “See also”- Cloud KMS signing (capability) — the how-to page: setup, configuration, and the key-custody boundary.
- Security — Deep Reference —
RemoteSigningSession,SequentialSigner, the PAdES B-B/B-T surface, and theSigningStrategycontract. - Signature — Deep Reference (Enterprise) — the B-LT/B-LTA long-term producer boundary.
- Security / Signing (Core) — the Core CMS signer and the contracts this surface extends.
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.