feat: IKeyOperations, an opaque private-key operations abstraction - #88
Merged
Merged
Conversation
Sign / per-block decrypt / metadata interface shaped after KMS and HSM APIs, sibling of CaSigner (which stays sign-only and issuance-scoped). Async by contract, with an optional sync fast path that local-key implementations provide so synchronous callers keep working unchanged. Metadata is declared rather than inferred, because an HSM-held key exposes nothing to inspect. Types, guards and the typed PrivateKeyUnavailableError only; LocalKeyOperations comes next.
The in-process implementation of IKeyOperations, sync fast path included, intended as the one place that dereferences the PrivateKey envelope. decryptLong/decryptLongSync ship the multi-block loop once: one block per provider call, concurrent decryption, ordered reassembly, misaligned buffers rejected. Unlike privateDecrypt_native, decryptBlock throws on failure; the swallow-into-garbage anti-oracle behavior belongs to callers, uniformly for local and remote keys. PKCS1v15 signatures byte-match node:crypto; PSS salts at digest length; metadata cross-checks rsaLengthPrivateKey for 2048/3072/4096.
Exposes an IKeyOperations as a CaSigner so one HSM/KMS integration serves both the secure-channel operations and X509 issuance. The concrete case is certificate renewal over an opaque key: a CSR whose proof-of-possession signature and embedded public key both come from the provider. Requires getPublicKey and says so; RSASSA-PKCS1-v1_5 with SHA-256 only, the hash parameter left in place so widening stays additive.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Introduces
IKeyOperations, an opaque private-key operations abstraction: an object that can use a private key (sign, decrypt) without the key ever being obtainable through it. It is the secure-channel/session-side sibling ofCaSigner(which stays sign-only and issuance-scoped), shaped after cloud KMS/HSM APIs so the OPC UA application instance key can live in a TPM, HSM, KMS or OS keystore, non-exportable.Three commits, purely additive, targeting a v5.10.0 minor:
aea088bIKeyOperationsinterface,AsymmetricSignParams(PKCS#1 v1.5 / PSS, SHA-1 / SHA-256),AsymmetricDecryptParams(PKCS#1 v1.5 / OAEP-SHA1 / OAEP-SHA256),KeyMetadata,isKeyOperations/hasSyncKeyOperationsguards, typedPrivateKeyUnavailableError855d164LocalKeyOperations(wraps thePrivateKeyenvelope, full sync fast path),keyOperationsFromPrivateKey,decryptLong/decryptLongSyncmulti-block helpers1700ac4caSignerFromKeyOperationsadapter: one HSM integration serves both channel operations and X509 issuance (CSR / self-signed / CRL)Design decisions
LocalKeyOperationsalso implementssignSync/decryptBlockSync/getKeyMetadataSync, so code paths that are synchronous by contract (OPC UA chunk assembly) keep bit-identical behavior with local keys. Callers pick a path viahasSyncKeyOperations.decryptBlockcall is one HSM/KMS operation;decryptLongships the multi-block loop once (concurrent decryption, ordered reassembly, misaligned buffers rejected) so providers never reimplement it.KeyMetadatatravels with the object, same philosophy asCaSigner.algorithm. RSA only today; the EC extension path (namedCurve, ECDSA params variant) is documented as additive.decryptBlockthrows on failure, unlikeprivateDecrypt_nativewhich swallows errors into a 1-byte buffer. A remote provider throws anyway; the swallow-into-garbage anti-oracle behavior belongs to the caller, applied uniformly to local and remote keys. Recorded in the TSDoc.RSA_PSS_SALTLEN_DIGEST), the convention the OPC UA Aes256_Sha256_RsaPss profile and WebCrypto verifiers expect.node:constantsmodule, notcrypto.constants, matchingcrypto_utils.ts: it is what the node-opcua-crypto-web browser shim knows how to satisfy (a namedconstantsimport fromnode:cryptobreaks the web bundle).caSignerFromKeyOperationsrequiresgetPublicKeyand says so: aCaSignermust produce its public half (CSR embeds it, an issued certificate's AKI derives from it) and an opaque key has no other source for it.Tests
30 new tests across three files; full suite 36 files, 309 passed, 6 skipped (pre-existing skips), zero behavioral change elsewhere.
node:cryptofor SHA-1 and SHA-256; PSS verifies at salt = 32 and fails at salt = 20, proving the convention is pinnedrsaLengthPrivateKeyfor RSA 2048 / 3072 / 4096; both envelope forms (PEM string, KeyObject) accepted; non-RSA keys rejected with a clear errordecryptLongreassembles by position, proven with a mock whose first block resolves last;decryptLongSyncrefuses async-only providerscreateCertificateFromCsryields a certificate that verifies against the CA key's public half and not against an unrelated key; an async-only provider signs a CSR over its own key with a spy proving all signing goes throughIKeyOperations.signContext
First step of the "Opaque Private Key Operations (HSM/KMS)" effort tracked on the org project boards. node-opcua-pki's
CertificateManagergains akeyOperationsoption next, then node-opcua adopts the abstraction for session/identity paths and the secure channel.🤖 Generated with Claude Code