Skip to content
getnextpdf.com

Pro edition

Cloud KMS signing — Deep Reference

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.

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.

SymbolParametersDefault behaviorReturnsThrows or fails withNotes
KmsSignerInterfaceExtends the Core HsmSignerInterface contractSPI for KMS and HSM drivers; reserved built-in ids: aws-kms, azure-keyvault, gcp-kms, pkcs11, openssl-cli
KmsSignerInterface::providerId()noneStable registry lookup keynon-empty-stringThird-party drivers must namespace their identifier
KmsSignerInterface::signWithVersion()$data, $algorithm = 'sha256WithRSAEncryption', $keyVersion = nullnull key version falls back to the provider defaultstring signature octets: RSA as returned by the provider (placed directly in SignerInfo.signature), ECDSA as DER ECDSA-Sig-Value per CMS rulesKeyManagementException, UnsupportedAlgorithmException, SignatureFailedExceptionPer-provider null semantics differ; see the behavior contract
KmsSignerInterface::supportsAlgorithm()string $algorithmCapability probe; performs no I/OboolCalled before provider selection
KmsSignerInterface::supportedAlgorithms()noneLists the OpenSSL-style names the provider acceptslist<non-empty-string>
AwsKmsSignerconstructor: AwsKmsConfig, cert DER, chain DER, PSR-18 client, PSR-17 factories, PSR-3 loggerAlgorithm defaults to KmsSigningAlgorithm::RsaPkcs1Sha256see methodsfinal; PROVIDER_ID = 'aws-kms'
AwsKmsSigner::create()key id, cert DER, PSR dependencies, optional chain, config, loggerBuilds AwsKmsConfig::fromEnvironment($keyId) when $config is nullselfReads the standard AWS_* environment variables
AwsKmsSigner::withAlgorithm()KmsSigningAlgorithm $algorithmReturns a modified cloneselfMust match the key type provisioned in AWS KMS
AwsKmsSigner::sign()$data, $algorithm = 'sha256WithRSAEncryption'Delegates to signWithVersion($data, $algorithm, null)stringas signWithVersion()Legacy two-argument Core contract path
AzureKeyVaultSignerconstructor: AzureKeyVaultConfig, cert DER, chain DER, PSR-18 client, PSR-17 factories, PSR-3 loggerAlgorithm defaults to AzureSigningAlgorithm::Rs256; a config access token seeds the bearer tokensee methodsfinal; PROVIDER_ID = 'azure-keyvault'
AzureKeyVaultSigner::create()vault name, key name, cert DER, PSR dependencies, optional chain, config, loggerBuilds AzureKeyVaultConfig::fromEnvironment() when $config is nullselfSupports pre-obtained token or service-principal credentials
AzureKeyVaultSigner::withAlgorithm()AzureSigningAlgorithm $algorithmReturns a modified cloneselfRSA keys use RS/PS values; EC keys use ES values
GcpKmsSignerconstructor: GcpKmsConfig, cert DER, chain DER, PSR-18 client, PSR-17 factories, PSR-3 loggerAlgorithm defaults to GcpKmsSigningAlgorithm::RsaSignPkcs1_2048Sha256see methodsfinal; PROVIDER_ID = 'gcp-kms', API_VERSION = 'v1'
GcpKmsSigner::create()project id, location, key ring, crypto key, cert DER, PSR dependencies, optional chain, config, loggerBuilds GcpKmsConfig::fromEnvironment() when $config is nullselfBearer-token acquisition is delegated to the caller
GcpKmsSigner::withAlgorithm()GcpKmsSigningAlgorithm $algorithmConfig-time preview only; the per-call wire name wins at sign timeselfKey size is fixed by the provisioned CryptoKeyVersion
AwsKmsSigningStrategy::__constructAwsKmsSigner $signerSynchronous; isAsync() returns falsePropagates the wrapped signer’s exceptionsAdapter for RemoteSigningSession::complete()
AzureKeyVaultSigningStrategy::__constructAzureKeyVaultSigner $signerSynchronous; isAsync() returns falsePropagates the wrapped signer’s exceptionsAdapter for RemoteSigningSession::complete()
KmsSigningAlgorithmenum, 9 cases (RSA PKCS#1, RSA-PSS, ECDSA; SHA-256/384/512)AWS KMS SigningAlgorithm wire valuesInvalidArgumentException from fromOpenSslName()resolveForWireName() preserves the configured PSS digest
AzureSigningAlgorithmenum, 9 cases (RS256ES512)Azure Key Vault JWA-style valuesInvalidArgumentException from fromOpenSslName()isEcdsa() marks values whose output needs DER conversion
GcpKmsSigningAlgorithmenum, 10 cases (EC P-256/P-384, RSA PKCS#1, RSA-PSS)GCP CryptoKeyVersion algorithm valuesUnsupportedAlgorithmException from fromOpenSslName()Wire-name resolution picks the smallest matching key size
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'): string
public 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): self
public 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): self
public function __construct(
private AwsKmsSigner $signer,
) {}
public function sign(string $signedAttributesDer): string
public function __construct(
private AzureKeyVaultSigner $signer,
) {}
public function sign(string $signedAttributesDer): string

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.

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.

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.

Providernull key versionEmpty stringOverride grammar
AwsKmsSignerUses AwsKmsConfig::$keyId; an alias or ARN resolves to the current key on the provider sideRejectedUUID (dashed or undashed), alias/<name>, or a KMS key/alias ARN
AzureKeyVaultSignerUses the configured key version; an empty config value selects the latest enabled version server-sideRejected32-character hexadecimal identifier
GcpKmsSignerUses the version pinned in GcpKmsConfig; with none pinned, raises KeyManagementExceptionRejectedDecimal 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.

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.

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.

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.

  • An empty-string key version is rejected on all three providers. Pass null to inherit the configured default.
  • A malformed key version is rejected before any request is built, with the offending value named in the exception.
  • AwsKmsSigner with an empty AwsKmsConfig::$keyId and a null key version raises KeyManagementException.
  • Provider responses that indicate a key-management failure map to KeyManagementException: AWS NotFoundException, DisabledException, KeyUnavailableException, InvalidKeyUsageException, or HTTP 404; Azure HTTP 404, KeyNotFound, KeyDisabled, or KeyNotActive; GCP HTTP 404 or 409, NOT_FOUND, FAILED_PRECONDITION, or an HTTP 400 whose message names a version.
  • Other non-200 provider responses raise SignatureFailedException on AWS and GCP, and AzureKeyVaultException on Azure.
  • A PSR-18 transport failure during signing maps to SignatureFailedException with the client exception preserved as the previous throwable.
  • AzureKeyVaultSigner with no access token and no service-principal credentials raises AzureKeyVaultException before any vault call. A failed Azure AD token acquisition also raises AzureKeyVaultException.
  • AzureKeyVaultSigner validates 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 with AzureKeyVaultException.
  • GcpKmsSigner with no OAuth2 bearer token raises SignatureFailedException; 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 missing value field raises AzureKeyVaultException).
  • A provider signature field that fails base64 decoding raises SignatureFailedException on AWS and GCP, and AzureKeyVaultException on Azure.
  • No SigningStrategy adapter for GcpKmsSigner ships in 3.1.0. The GCP signer is consumed through the KmsSignerInterface contract directly.

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.

ClaimStandardClause
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 3161Appendix 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.

  • Availability within the Pro package: AwsKmsSigner since 1.9.0, AzureKeyVaultSigner since 2.0.0, GcpKmsSigner and KmsSignerInterface since 2.1.0. All are current in nextpdf/pro 3.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 KmsSignerInterface and must namespace their providerId() to avoid collisions with the reserved built-in identifiers.

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.