Under the hood
Cryptography & key storage
The mandatory algorithm profile, private-key protection, fingerprints, and host pins.
60c72c2bView original On this page
psh uses the vendored OpenSSL implementation for key generation, signatures, password-based key encryption, and DTLS. It does not implement its own encryption or key derivation primitives.
Connection authentication
The connection policy requires DTLS 1.3, TLS_CHACHA20_POLY1305_SHA256, mandatory
X25519MLKEM768, Ed25519 server raw-public-key authentication, and ALPN psh/3.
Tickets, resumption, early data, compression and legacy fallback remain disabled.
OpenSSL’s hybrid key exchange and HKDF-SHA-256 schedule derive handshake and
application secrets; ChaCha20-Poly1305 authenticates and encrypts records.
Both host and user identities require Ed25519 and ML-DSA-87. This is a connection-bound psh authentication construction, not a native composite TLS signature or an OpenSSH format. All primitives are supplied by pinned OpenSSL 4.1.0; ML-DSA uses normal Pure signing with empty context and default message encoding, never external-mu or test-entropy modes.
The broker retains both host private keys. The existing worker signing provider
holds only the Ed public key and its restricted IPC callback. At most two server
CertificateVerify signatures are permitted, each covering exactly 64 spaces,
TLS 1.3, server CertificateVerify, one NUL, and a 32-byte SHA-256 transcript hash.
After TLS, the broker constructs one separate ML-DSA host proof bound to its own
complete identity, supervisor CID, immutable exporter, and fresh host nonce.
No subsequent TLS or host-proof signing request is permitted.
The client checks the PQ proof with the actual TLS peer’s Ed public key and its own exporter/CID, then trusts the complete pair through a provisioned key or explicit first-use policy. Only then may it disclose OPEN/user/command data. The broker independently authorizes the exact client pair from one authorized file, challenges once, and verifies both signatures over the same transcript. That transcript binds the entire host identity and SHA-512 of the exact OPEN. See the exact encodings, domains and state rules .
The exporter remains a worker assertion to the broker; preceding TLS signatures are not independent proof of a completed handshake. The structured signing capability does not make a compromised endpoint trustworthy. Independent broker user authorization is preserved. First-use possession proofs also cannot replace trusted provisioning or independent verification of the complete-pair fingerprint.
The intended authentication property assumes the connection binding, hashes and hybrid key exchange remain secure: compromise of one signature component still leaves the other mandatory verification. This is not a formal security proof or an assertion of whole-protocol category 5/CNSA compliance or OpenSSH equivalence.
DTLS KeyUpdate derives new traffic secrets from current secrets; it does not perform fresh key agreement or recover from current traffic-secret disclosure. The client coordinates updates of both directions after one hour or 1,048,576 authenticated application records sent and received combined, as observed by the client. Lost server datagrams do not advance this trigger, so it is not a strict per-server encryption-operation ceiling under loss. The server responds to these requests; it does not independently schedule potentially overlapping updates. Upgrade both endpoints to obtain this policy; older servers still schedule their own updates, and older clients lack the combined count.
Files, fingerprints, and migration
Private identity files contain exactly two PKCS#8 PEM objects: Ed25519 followed
by ML-DSA-87. Public .pub files contain the corresponding two SPKI PEM objects
in the same order. Only whitespace may precede, separate or follow them. Missing,
reordered, unsupported, duplicate, additional or mixed-encryption-mode objects
are rejected. Each PEM payload must contain exactly one complete ASN.1 object,
including both the encrypted envelope and its decrypted PKCS#8 contents.
These paired files are distinct from OpenSSH private/authorized-key
formats. Existing single Ed25519 files cannot be used as identities in psh/3.
Fingerprints are SHA512: plus unpadded Base64 of the 64-byte digest of
"psh-identity-ed25519-mldsa87-v1\0" || Ed_public[32] || ML_public[2592].
The domain includes its NUL. This binds both components and the fixed profile;
one component is never enough. Both private and public files yield the same
fingerprint. It is not interchangeable with psh/2 or OpenSSH fingerprints.
Upgrade both endpoints, generate fresh paired identities, provision complete
client public pairs, independently verify new host fingerprints, and deliberately
remove/replace old host pins. No keys are converted or overwritten automatically.
Legacy SHA256: trust-store records are parsed for diagnosis/removal only; they
never authenticate the new pair, even when its Ed component happens to match.
Private files must be regular files owned by the expected account with mode 0600 or stricter; host private files are root-owned. Protected public files must have the expected owner and no group/world write permission. Symlinks, NULs, special files, growth beyond 64 KiB, and ignored PEM objects are rejected. Generation writes a complete private bundle and complete public bundle separately through flushed exclusive publication. A failed public publication removes only the private file just created, never an existing file. A crash between publications can leave a complete private bundle without its public companion.
psh-owned plaintext snapshots, encoded private buffers and intermediate PKCS#8 private bytes are cleared before release. OpenSSL owns decoded private objects until freed. Passphrases are prompted once for both components and cleared by the caller. Worker and session-relay exec boundaries discard inherited memory for both private components. Host/user identities should be distinct fresh pairs.
Resource bounds
The UDP ceiling remains 1,200 bytes including the 22-byte envelope; DTLS MTU is 1,178 bytes. DTLS handles fragmented hybrid key shares; application proofs use the bounded reliable stream. Input/output BIO rings are each 65,536 bytes, including internal queue metadata. The 16,384-byte certificate-list setting retains the pinned no-comp build’s generic handshake-message ceiling of 16,716 bytes; it is not an exact 16 KiB ceiling. Future handshake messages are bounded to ten ahead, with at most 1,023 retained ACK record numbers. Dependency updates must recheck those derived bounds.
Application OPEN is at most 10,887 bytes, frames at most 11,015, host proof exactly 7,251, and client proof exactly 4,691. Existing 64 KiB stream/output queues, 16 pending authentications and 64 total connections remain unchanged. DTLS and client host-proof receipt each have 15-second deadlines. The broker initial 60-second budget is not refreshed by host proof; user challenge has 15 seconds. The supervisor’s existing 65-second outer admission deadline may shorten a late challenge. None of these deadlines is extended by unauthenticated traffic.
Optional local passphrase encryption
A nonempty passphrase causes key generation to write both PKCS#8 PEM objects encrypted in one file using OpenSSL’s standard PBES2 construction:
- PBKDF2-HMAC-SHA256 with 600,000 iterations and an independently generated fresh 16-byte salt for each object derives the encryption key.
- AES-256-CBC encrypts each component’s PKCS#8 private-key contents, with its IV and derivation parameters encoded in the standard file format.
An omitted or empty passphrase produces an unencrypted PKCS#8 file. Unencrypted host keys support unattended server startup and depend on root ownership, restrictive file permissions, and host security for protection at rest. Client identities can use a passphrase when local interactive unlocking is appropriate.
PBKDF2 is a local password-stretching function. It is distinct from the connection’s HKDF, which derives traffic secrets from cryptographic handshake material. The current file format does not use Argon2 or scrypt. The passphrase encryption parameters do not weaken or negotiate the connection’s required DTLS algorithms. PKCS#8 AES-CBC encryption does not provide an authenticated-encryption tag; retain the same ownership and permission protections for encrypted key files.
Host pins
The known-hosts file is a separate, strict text format containing one lowercase host, decimal port, and psh fingerprint per line, separated by tabs. Each line must end with a newline. The parser rejects malformed records, duplicates, invalid fingerprints, unsafe ownership or permissions, symlinks, and stores larger than 64 KiB or 1,024 records.
A previously unseen host requires an explicit terminal confirmation of its fingerprint, or a matching public key provisioned through a separate trusted channel. Batch mode rejects an unknown host. A changed host key is rejected even if a new public key was supplied; remove the old pin deliberately before establishing a replacement.
Trust operations synchronize through the protected known_hosts.lock sidecar file. Keep this file alongside the store. Explicit pin deletion writes and flushes a replacement before atomically publishing it, while the shared sidecar lock prevents concurrent additions from being lost.
Interactive confirmation releases the store and sidecar lock while waiting for human input. After acceptance, the client reacquires the lock, reopens the current store, and validates it again. A matching concurrent pin is reused; a conflicting pin is rejected. Unrelated additions and atomic deletions remain intact.