Skip to content

TLS 1.3 (SPP 9.0): opt-in min/max version pins + cert TLS 1.2 auto-cap (8.2.0) - #57

Draft
DanPeterson wants to merge 4 commits into
OneIdentity:mainfrom
petrsnd:danpeterson-tls-min-max-version
Draft

TLS 1.3 (SPP 9.0): opt-in min/max version pins + cert TLS 1.2 auto-cap (8.2.0)#57
DanPeterson wants to merge 4 commits into
OneIdentity:mainfrom
petrsnd:danpeterson-tls-min-max-version

Conversation

@DanPeterson

Copy link
Copy Markdown
Contributor

Summary

Fixes #46 and #48 together. SPP 9.0 enables TLS 1.3, which moves client-certificate authentication to a post-handshake exchange (RFC 8446 §4.6.2). On TLS 1.3 the appliance requests the client cert after the handshake; the client must answer that CertificateRequest.

Node.js has no client-side post-handshake authentication. The upstream request (nodejs/node#46120) was closed NOT_PLANNED, and tls.connect exposes no toggle for it. So over a plain TLS 1.3 connection the cert is never presented and cert/A2A auth fails against SPP 9.0 with 60094 Authorization is denied. Password/token auth is unaffected.

Unlike the PySafeguard fix (which enables Python's post_handshake_auth), Node cannot do PHA at all — so this SDK uses a TLS min/max version strategy instead (mirroring the pysafeguard/SafeguardJava min/max API shape).

Changes

1. Opt-in TLS version pins

TlsOptions (on NodeHttpClient) gains minVersion / maxVersion (TlsVersion = 'TLSv1.3' | 'TLSv1.2' | 'TLSv1.1' | 'TLSv1', default unset = negotiate). Also adds pfx to TlsOptions so PFX/PKCS12 mTLS material actually reaches the undici Agent.

2. Cert-auth works by default (auto-cap at TLS 1.2)

New resolveTlsConnectOptions() in src/http/node.ts: when a client cert (cert/key/pfx) is present and neither bound is pinned, the connection is capped at TLSv1.2 so the cert is requested in-handshake. This keeps certificate/A2A auth working by default on SPP 9.0's Standard binding. Password/token connections carry no cert and keep negotiating TLS 1.3.

3. TLS 1.3 cert-auth via Cert SNI

Setting minVersion/maxVersion explicitly disables the auto-cap. For TLS 1.3 cert-auth, target the appliance's Cert SNI hostname (in-handshake cert request) and pin minVersion: 'TLSv1.3'.

4. HTTP/1.1

undici stays on HTTP/1.1 (its default); allowH2 is never enabled — HTTP/2 disallows the post-handshake CertificateRequest.

5. Version

Bumped 8.1.08.2.0 (backward-compatible minor).

Usage

import { SafeguardClient, CertificateAuth, NodeHttpClient } from '@oneidentity/safeguard';

const auth = new CertificateAuth({ certFile: './client.pem', keyFile: './client.key' });
const client = new SafeguardClient('safeguard.corp.example', { auth });

// cert present + no pin => auto-capped at TLS 1.2 (works on 9.0 Standard binding)
client.setHttpClient(new NodeHttpClient(auth.getTlsOptions()));

// TLS 1.3 cert-auth via the Cert SNI hostname:
// new NodeHttpClient({ ...auth.getTlsOptions(), minVersion: 'TLSv1.3' });

Tests

New unit tests (tests/unit/http-node.test.ts) cover the version plumbing and the cert auto-cap (default cap, no-cap for password/token, Cert SNI override, explicit-pin override, mTLS forwarding). Full suite: 162 passed; typecheck and build clean; lint 0 errors.

Docs

README (TLS 1.3 and SPP 9.0 section with JS gotchas), AGENTS.md, the api-patterns / a2a-workflow / architecture skills, and the certificate-auth sample.

⚠️ Before merge — live validation pending

Not yet validated against live appliances. Please confirm on real hardware:

  • SPP 9.0 (TLS 1.3): default cert-auth + A2A retrieval succeed (auto-cap TLS 1.2 on the Standard binding).
  • SPP 9.0 Cert SNI: cert-auth succeeds at TLS 1.3 with minVersion: 'TLSv1.3'.
  • SPP 8.x (TLS 1.2): no regression for cert/A2A auth.
  • Password/token auth negotiates TLS 1.3 on 9.0.

Follow-up: add appliance-backed cert/A2A integration tests (per #46) once a 9.0 appliance is available in CI.

SPP 9.0 enables TLS 1.3, which moves client-certificate auth to a
post-handshake exchange (RFC 8446 4.6.2). Node has no client-side
post-handshake authentication (nodejs/node#46120, closed NOT_PLANNED), so
over TLS 1.3 the cert is never presented and cert/A2A auth fails with
60094. Unlike PySafeguard we cannot enable PHA, so we use TLS-version
control instead.

- Add opt-in minVersion/maxVersion (TlsVersion) + pfx to TlsOptions.
- resolveTlsConnectOptions() plumbs pins into the undici Agent and
  auto-caps cert connections (cert/key/pfx) at TLSv1.2 when neither bound
  is pinned, so cert/A2A auth works by default on the Standard binding.
  Password/token connections keep negotiating TLS 1.3. TLS 1.3 cert-auth
  is reachable via the Cert SNI hostname with minVersion TLSv1.3.
- Unit tests for the version/auto-cap logic.
- Docs: README TLS 1.3/SPP 9.0 section, AGENTS.md, api-patterns +
  a2a-workflow + architecture skills, certificate-auth sample.
- Bump 8.1.0 -> 8.2.0.

Fixes OneIdentity#46, OneIdentity#48
undici negotiates HTTP/2 via ALPN by default, but the appliance's HTTP/2-capable Standard binding rejects client-certificate auth (cert auth and A2A) with HTTP_1_1_REQUIRED, independent of TLS version. Disable HTTP/2 on the undici Agent whenever a client certificate is present, completing the cert-auth support for issue #650 (the TLS-version cap alone was not sufficient). Password/token connections carry no certificate and keep HTTP/2.
The A2A client's list, retrieve, and write paths had never been exercised against a live appliance (no live A2A coverage) and were incorrect:

- getRetrievableAccounts targeted the a2a service with a single request; retrievable accounts live under the core service as a two-step enumeration (registrations, then each registration's retrievable accounts), authorized by the client certificate.
- retrievePassword/retrievePrivateKey returned the raw response body, so callers received a JSON-quoted string; decode the JSON string.
- setPassword used Credentials?type=Password (405); the write endpoint is Credentials/Password.

Add live certificate-auth and A2A integration tests (mirroring the other SDK suites) plus their bootstrap fixtures, and run integration files serially since they share one appliance and a single fixed client certificate. Update the A2A unit tests to the corrected wire contract.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support TLS 1.3 mTLS: add TLS-version option for cert/A2A + integration tests

2 participants