Skip to content

Cryptography and TLS

kazah-png edited this page Jul 26, 2026 · 7 revisions

Cryptography and TLS

NyxOS speaks TLS 1.2 to real HTTPS servers, using cryptographic primitives written from scratch in the kernel. It performs an ECDHE key agreement, encrypts with AES-128-GCM, verifies the server's ServerKeyExchange signature, and validates the certificate chain to a pinned trust anchor — with hostname and validity-date checks.

Every primitive here has a known-answer self-test runnable from the shell, checked against the published vectors for that algorithm.

Do not trust this with anything real. These implementations have never been reviewed by anyone outside the project, no constant-time behaviour is claimed, and the trust store holds five anchors rather than a real root set. See Security.

See also: Networking Stack, Selene Browser, Security, Shell

The primitives

Area File Provides Self-test
Hashing sha256.c SHA-256, HMAC-SHA256, PBKDF2
Hashing sha512.c SHA-512, SHA-384 (FIPS 180-4) sha512test
AEAD aes_gcm.c AES-128-GCM seal/open gcmtest
ECDH curve25519.c X25519 (RFC 7748) x25519test
ECC p256.c secp256r1 arithmetic, ECDSA-P256 verify p256test
ECC p384.c secp384r1 arithmetic, ECDSA-P384/SHA-384 verify p384test
RSA rsa.c PKCS#1 v1.5 and PSS signature verification rsatest
RNG csprng.c HMAC_DRBG-SHA256 (NIST SP 800-90A) csprngtest
ASN.1 der.c DER reader dertest
PKI x509.c Certificate-chain verification, SAN and date checks chaintest
KDF tls_prf.c TLS 1.2 PRF, P_SHA256 prftest
Protocol tls.c Handshake, key schedule, record layer tlskeytest, tlsrectest, skp384test

Randomness

csprng_bytes(out, n) is an HMAC_DRBG-SHA256, instantiated on first use from the best available hardware entropy — RDSEED/RDRAND, plus RDTSC jitter and the tick counter — and periodically reseeded. The TLS ClientHello random and every ephemeral key come from it.

This is a different thing from /dev/random, which is a plain xorshift64 PRNG suitable for scripts and nothing else. See Filesystem.

AEAD

aes128_gcm_encrypt(key, iv12, aad, aad_len, pt, pt_len, ct, tag16);
int ok = aes128_gcm_decrypt(key, iv12, aad, aad_len, ct, ct_len, tag, pt);

decrypt verifies the tag first and only writes the plaintext if it is valid — on failure the output buffer is left untouched.

Signature verification

int p256_ecdsa_verify(Qx, Qy, hash, r, s);              // 0 = valid
int p384_ecdsa_verify(Qx, Qy, hash48, r, s);
int rsa_pkcs1_sha256_verify(N, Nlen, e, sig, siglen, hash);
int rsa_pss_sha256_verify(N, Nlen, e, sig, siglen, mHash);

The TLS 1.2 handshake

ClientHello        →   TLS 1.2, CSPRNG random, SNI, cipher list, supported_groups
                   ←   ServerHello          (chosen cipher suite)
                   ←   Certificate          (the chain)
                   ←   ServerKeyExchange    (ECDHE params + signature)
                   ←   ServerHelloDone
                        │
                        ├─ verify the chain to a pinned root      (x509.c)
                        ├─ check the hostname against the SAN     (x509.c)
                        ├─ check the validity dates against RTC   (x509.c)
                        └─ verify the SKE signature under the leaf key
                        │
ClientKeyExchange  →   our ECDHE public value
ChangeCipherSpec   →
Finished           →   verify_data over the handshake transcript
                   ←   ChangeCipherSpec
                   ←   Finished
        ═══════ AES-128-GCM application records ═══════

Offered cipher suites

Value Suite
0xC02F ECDHE_RSA_WITH_AES_128_GCM_SHA256
0xC02B ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
0xC030 ECDHE_RSA_WITH_AES_256_GCM_SHA384
0xC02C ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
0x009C / 0x009D RSA_WITH_AES_*_GCM_*
0x002F / 0x0035 RSA_WITH_AES_*_CBC_SHA

Supported groups

x25519, secp256r1 (P-256), secp384r1 (P-384). P-256 matters in practice — a large fraction of servers prefer it and will not negotiate x25519.

Key schedule and records

The TLS 1.2 PRF (P_SHA256) derives the master secret from the ECDHE pre-master secret and the two randoms, then expands it into the key block. Records are framed on the wire as:

explicit_nonce(8) || GCM_ciphertext || GCM_tag(16)

tlskeytest and tlsrectest check the schedule and the record layer, including the Finished verify_data, against known answers.

The trust model (x509.c)

Chain verification takes the certificates the server sent, verifies each under the next one's public key, and requires the top to carry a pinned trusted-root public key from x509_roots.h.

Result Meaning
X509_OK (0) Every link verified and the top is a pinned trusted root
X509_INCOMPLETE (1) Links checked, but not anchored — unknown root or an unsupported signature algorithm. Not a detected forgery
X509_FORGED (−1) A link using a supported algorithm failed cryptographic verification

That three-way distinction is deliberate. "I could not check this" and "I checked this and it is forged" are different statements, and collapsing them would either cry wolf or hide an attack.

Two further checks complete "verify the peer":

  • x509_check_host — matches subjectAltName dNSName entries case-insensitively, honouring a single leading *. wildcard label
  • x509_check_validity — checks the certificate is inside its validity window, per the RTC

Bundled trust anchors

Five, in x509_roots.h:

Anchor Key
SSL.com TLS ECC Root CA 2022 EC
Amazon Root CA 1 RSA-2048
DigiCert Global G2 TLS RSA SHA256 2020 CA1 RSA-2048
GTS Root R4 EC
Root YR RSA-4096

chaintest runs a real chain (accept), a one-byte-tampered copy (reject as forged), and a root-less prefix (reject as not-anchored).

Strict mode

tlsstrict on

Off by default. When on, a handshake is refused unless:

  1. The chain anchors to a pinned root
  2. The hostname matches the certificate
  3. The certificate is in date
  4. The ServerKeyExchange signature verifies

With it off, failures are reported and the connection proceeds — useful for development, useless as a defence. Strict mode is what turns the trust model from "reported" into "blocked".

API

int tls_hello(const char* host, int iface_idx);

int tls_https_fetch(const char* host, const char* path, int iface_idx,
                    uint8_t* out, uint32_t cap, int verbose);

int tls_https_request(const char* host, const char* path, const char* method,
                      const uint8_t* body, uint32_t body_len, int iface_idx,
                      uint8_t* out, uint32_t cap, int verbose);

void tls_set_strict(int on);
int  tls_get_strict(void);

tls_https_request sends a non-NULL body as application/x-www-form-urlencoded with a Content-Length — this is what backs POST forms in Selene Browser.

Receiving is streaming, so pages larger than the record buffer load correctly.

Trying it

tls example.com          # handshake against host:443, with detail
tlsstrict on             # then repeat — a failing chain now refuses
posttest                 # live HTTP POST over TLS to httpbin
httpget https://…        # fetch and print
selene                   # the browser, over HTTPS

History

The whole stack was built in sequence between v5.9.48 and v5.9.80, one primitive per release — ClientHello, X25519, the PRF, ECDHE, AES-GCM, the record layer, a live handshake, then the trust model (DER, ECDSA-P256, RSA, SHA-512, ECDSA-P384, chain verification, hostname and dates), then enforcement and the wider trust store. Version History has the release-by-release account.

See also

External resources

Clone this wiki locally