test(tls): rv2 8 phase E — both AEADs cross-checked against openssl over the wire
- tls-server-accept.sh: each probe pins openssl s_client's -ciphersuites — ec/ChaCha20-Poly1305, rsa/AES-128-GCM, plus openssl's default list whose first suite (AES-256-GCM) the server must skip — 5/0 - tls-accept.sh: the Python/OpenSSL stub prints the negotiated suite; the happy-path ok line carries it — 5/0 (ChaCha under the peer's server-preference default) - rv2 8 story: E landed (real-protocol interop replaces the infeasible `openssl enc` AEAD check); D (encrypted-cookie wrapper) re-homed to porch as the consumer's phase after porch 2 — fork auto-approved, review_pending; status: done - porch 2: the encrypted-cookie out-of-scope bullet now points at the landed primitives and names the wrapper as its follow-on - board row rv2 8: in-progress -> done Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> (cherry picked from commit ac3bf74da4f45f624d7d3b440c8bcf17f4aec3a9)
This commit is contained in:
parent
e1b9ada190
commit
dd426d0581
5 changed files with 31 additions and 18 deletions
|
|
@ -1612,7 +1612,7 @@ starts. Edges in [dependency graph section 6](../00-dependency-graph.md).
|
||||||
| 5 | [fd passing](runtime-v2/05-fd-passing.md) | ✅ **DONE 2026-09-02** — `net.send_fd`/`recv_fd`/`connect_unix`; a tty crossed the socket, was raw'd through the received copy and restored at destroy — the wmux handover in miniature |
|
| 5 | [fd passing](runtime-v2/05-fd-passing.md) | ✅ **DONE 2026-09-02** — `net.send_fd`/`recv_fd`/`connect_unix`; a tty crossed the socket, was raw'd through the received copy and restored at destroy — the wmux handover in miniature |
|
||||||
| 6 | [term.size + term.width](runtime-v2/06-term-size-width.md) | ✅ **DONE 2026-09-02** — TIOCGWINSZ read twin (nil = not a tty) and libc wcwidth under C.UTF-8; the only runtime work the whole wmux parity ladder needs |
|
| 6 | [term.size + term.width](runtime-v2/06-term-size-width.md) | ✅ **DONE 2026-09-02** — TIOCGWINSZ read twin (nil = not a tty) and libc wcwidth under C.UTF-8; the only runtime work the whole wmux parity ladder needs |
|
||||||
| 7 | [observability](runtime-v2/07-observability.md) | ⬜ **`ready` 2026-09-09** (was `refine`; moved here 2026-09-06 from language iteration 30). Four forks locked, grounded in `runtime/src`: (1) **counters/gauges only** — they already exist as fields (`gc_traced_cnt`, `gc_alloc_bytes`, `gc_step_no`, arena `used`, `nfibers`, `nchildren`, `ntls`); profiling split to its own later iteration; (2) **Prometheus text rendered in `.wo`** from a `map<Text,Int>` (no new record, no JSON); (3) **pull** via `proc.metrics() -> map<Text,Int>`, per shard; push/OTel deferred by name; (4) **stack-trace-on-trap lands first, alone** — the trap path already has method+line (per-method line table) and `vm_unwind` walks frames, so phase A appends `at METHOD line N` per frame under the existing trap line. Phases A trace → B `proc.metrics()` → C porch mounts `/metrics` (porch's). Builtin id confirmed against `WO_B_MAX` at build (shared enum). Consumers: porch [8](porch/08-static-and-lifecycle.md)/[39](language-runtime-database/39-web-framework-parity.md), databasev2 [5](databasev2/05-bounded-tables-eviction.md), the limiter's lazy expiry. Forks auto-approved, `review_pending` |
|
| 7 | [observability](runtime-v2/07-observability.md) | ⬜ **`ready` 2026-09-09** (was `refine`; moved here 2026-09-06 from language iteration 30). Four forks locked, grounded in `runtime/src`: (1) **counters/gauges only** — they already exist as fields (`gc_traced_cnt`, `gc_alloc_bytes`, `gc_step_no`, arena `used`, `nfibers`, `nchildren`, `ntls`); profiling split to its own later iteration; (2) **Prometheus text rendered in `.wo`** from a `map<Text,Int>` (no new record, no JSON); (3) **pull** via `proc.metrics() -> map<Text,Int>`, per shard; push/OTel deferred by name; (4) **stack-trace-on-trap lands first, alone** — the trap path already has method+line (per-method line table) and `vm_unwind` walks frames, so phase A appends `at METHOD line N` per frame under the existing trap line. Phases A trace → B `proc.metrics()` → C porch mounts `/metrics` (porch's). Builtin id confirmed against `WO_B_MAX` at build (shared enum). Consumers: porch [8](porch/08-static-and-lifecycle.md)/[39](language-runtime-database/39-web-framework-parity.md), databasev2 [5](databasev2/05-bounded-tables-eviction.md), the limiter's lazy expiry. Forks auto-approved, `review_pending` |
|
||||||
| 8 | [symmetric cipher (AEAD)](runtime-v2/08-symmetric-cipher.md) | 🔄 **in-progress** — the **first rung of the TLS ladder** (gates rv2 9). **Phases A + B + C LANDED 2026-09-08**: A ChaCha20-Poly1305 (ids 111/112, RFC 8439 §2.8.2); B AES-128/256-GCM (ids 113/114) via AES-NI+PCLMULQDQ; C portable constant-time software AES-GCM fallback (S-box via GF-inverse ladder, bit-by-bit GHASH) — AES-GCM now on any CPU, dispatched hw-or-sw. All hand-rolled, constant-time, both AES paths NIST cases 4 & 16 byte-exact, KAT-gated in test_crypto (**48/0**), ASan/UBSan clean. Remaining: D cookie wrapper → E gate (ARMv8 hw path deferred). Consumers: rv2 9 TLS + porch encrypted cookies |
|
| 8 | [symmetric cipher (AEAD)](runtime-v2/08-symmetric-cipher.md) | ✅ **DONE 2026-09-09** (`review_pending`: phase D re-homed to porch) — the **first rung of the TLS ladder**. A ChaCha20-Poly1305 (ids 111/112, RFC 8439 §2.8.2); B AES-128/256-GCM (ids 113/114) via AES-NI+PCLMULQDQ; C portable constant-time software AES-GCM fallback — AES-GCM on any CPU, dispatched hw-or-sw. All hand-rolled, constant-time, KAT-gated in test_crypto, ASan/UBSan clean. **E landed 2026-09-09**: the reference cross-check is real-protocol interop — `just tls-server` pins `openssl s_client` to ChaCha, to AES-128-GCM, and to openssl's default list (5/0); `just tls` reports the negotiated suite (5/0). **D (encrypted-cookie wrapper) is porch's phase**, pure `.wo` after porch 2 ships `random_bytes` + `SetCookie`. ARMv8 hw path deferred until an ARM host exists |
|
||||||
| 9 | [in-process TLS](runtime-v2/09-in-process-tls.md) | ✅ **DONE 2026-09-09** — in-process TLS 1.3 **both directions**, **retired the "TLS is the proxy's job" doctrine** (34/38/porch corrected). Hand-rolled, 1.3-only, RSA+ECDSA+full X.509; KAT'd vs **RFC 8448** / real certs, ASan/UBSan clean. **A–E crypto** (AEAD, HKDF, X25519, sign/verify, X.509 + SAN + basicConstraints/EKU) → **F client** (`net.connect_tls`/`read_tls`/`write_tls`, ids 115–117) → **G server** (constant-time RSA-PSS + ECDSA-P256 signing w/ RFC 6979, server FSM, `net.accept_tls` id 118, `wo_pkey_parse`). Live-gated: `just tls` 5/0 (outbound) + `just tls-server` 4/0 (inbound, openssl s_client EC+RSA). test_tls 123/0, test_crypto 130/0, full suite 0 fail. Deferred follow-ups (non-blocking): park-based handshake, `TlsConn` object, connection pooling, close_notify, complete-formula EC ladder. Forks auto-approved 2026-09-08/09, `review_pending`. The project's **highest-risk** work — done |
|
| 9 | [in-process TLS](runtime-v2/09-in-process-tls.md) | ✅ **DONE 2026-09-09** — in-process TLS 1.3 **both directions**, **retired the "TLS is the proxy's job" doctrine** (34/38/porch corrected). Hand-rolled, 1.3-only, RSA+ECDSA+full X.509; KAT'd vs **RFC 8448** / real certs, ASan/UBSan clean. **A–E crypto** (AEAD, HKDF, X25519, sign/verify, X.509 + SAN + basicConstraints/EKU) → **F client** (`net.connect_tls`/`read_tls`/`write_tls`, ids 115–117) → **G server** (constant-time RSA-PSS + ECDSA-P256 signing w/ RFC 6979, server FSM, `net.accept_tls` id 118, `wo_pkey_parse`). Live-gated: `just tls` 5/0 (outbound) + `just tls-server` 4/0 (inbound, openssl s_client EC+RSA). test_tls 123/0, test_crypto 130/0, full suite 0 fail. Deferred follow-ups (non-blocking): park-based handshake, `TlsConn` object, connection pooling, close_notify, complete-formula EC ladder. Forks auto-approved 2026-09-08/09, `review_pending`. The project's **highest-risk** work — done |
|
||||||
|
|
||||||
### ▸ wmux — the terminal multiplexer track
|
### ▸ wmux — the terminal multiplexer track
|
||||||
|
|
|
||||||
|
|
@ -217,10 +217,14 @@ randomness and cookies maps onto primitives writeonce already has.
|
||||||
|
|
||||||
## Out Of Scope
|
## Out Of Scope
|
||||||
|
|
||||||
- **Encrypted cookies.** Fiber's `encryptcookie` needs a symmetric cipher
|
- **Encrypted cookies.** Fiber's `encryptcookie` needs a symmetric cipher.
|
||||||
(AES-GCM), and the runtime has digests only. Signed-and-readable is honest and
|
Signed-and-readable is honest and sufficient for a session id, so this
|
||||||
sufficient for a session id; encrypting a payload is a separate ask with a
|
iteration ships without it — but the primitive now exists
|
||||||
separate primitive behind it.
|
([runtime-v2 8](../runtime-v2/08-symmetric-cipher.md): `chacha20poly1305_seal`/
|
||||||
|
`open`, `aes_gcm_seal`/`open`, done 2026-09-09). The wrapper — random nonce
|
||||||
|
from `random_bytes` prepended to the ciphertext, default ChaCha — is rv2 8's
|
||||||
|
**phase D, re-homed here as porch's follow-on** to this iteration: pure `.wo`
|
||||||
|
on the `SetCookie` machinery and `random_bytes` this iteration builds.
|
||||||
- **UUID-formatted ids.** fiber's `UUIDv4` is a format over the same entropy
|
- **UUID-formatted ids.** fiber's `UUIDv4` is a format over the same entropy
|
||||||
`random_bytes` provides; a base64'd 32-byte token is stronger and needs no new
|
`random_bytes` provides; a base64'd 32-byte token is stronger and needs no new
|
||||||
builtin. A UUID *format* helper, if ever wanted, is pure `.wo`.
|
builtin. A UUID *format* helper, if ever wanted, is pure `.wo`.
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
---
|
---
|
||||||
track: runtime-v2
|
track: runtime-v2
|
||||||
iteration: "8"
|
iteration: "8"
|
||||||
status: in-progress
|
status: done
|
||||||
readiness: ready
|
readiness: ready
|
||||||
|
review_pending: "fork auto-approved 2026-09-09 for autonomous execution — developer second review: phase D (the encrypted-cookie wrapper) re-homed to the porch track as the consumer's phase (pure .wo on porch 2's cookie machinery + random_bytes, which do not exist yet), the same split rv2 7 makes for its /metrics phase; the runtime primitive closes here with phase E"
|
||||||
---
|
---
|
||||||
|
|
||||||
# runtime-v2 8 — AEAD ciphers: authenticated encryption for cookies, data at rest, and TLS
|
# runtime-v2 8 — AEAD ciphers: authenticated encryption for cookies, data at rest, and TLS
|
||||||
|
|
@ -81,8 +82,8 @@ work; TLS's ChaCha suite and the cookie consumer unblock at phase A.
|
||||||
| A — ChaCha20-Poly1305 | ✅ **LANDED 2026-09-08** — `chacha20poly1305_seal`/`open` (ids 111/112, bare-name crypto family). Hand-rolled ChaCha20 + poly1305-donna-32 + the RFC 8439 §2.8 AEAD, caller-supplied 12-byte nonce, 32-byte key, constant-time tag compare, `open` returns nil on auth failure. Matches the RFC 8439 §2.8.2 vector byte-for-byte; gated in `test/test_crypto.c` (§2.5.2 Poly1305 + §2.8.2 seal/open/tamper), ASan/UBSan clean |
|
| A — ChaCha20-Poly1305 | ✅ **LANDED 2026-09-08** — `chacha20poly1305_seal`/`open` (ids 111/112, bare-name crypto family). Hand-rolled ChaCha20 + poly1305-donna-32 + the RFC 8439 §2.8 AEAD, caller-supplied 12-byte nonce, 32-byte key, constant-time tag compare, `open` returns nil on auth failure. Matches the RFC 8439 §2.8.2 vector byte-for-byte; gated in `test/test_crypto.c` (§2.5.2 Poly1305 + §2.8.2 seal/open/tamper), ASan/UBSan clean |
|
||||||
| B — AES-GCM via hardware | ✅ **LANDED 2026-09-08** (x86-64) — `aes_gcm_seal`/`open` (ids 113/114), AES-128/256 (by key length) on AES-NI + PCLMULQDQ, constant-time by hardware, CPUID-gated with target-attributed functions so the binary stays portable (no-AES-NI traps until phase C). Matches NIST SP 800-38D cases 4 & 16 byte-for-byte; KAT-gated in `test_crypto.c`, ASan/UBSan clean. **ARMv8 crypto-ext path deferred** (untestable on the x86-64 dev host) — folds into phase C |
|
| B — AES-GCM via hardware | ✅ **LANDED 2026-09-08** (x86-64) — `aes_gcm_seal`/`open` (ids 113/114), AES-128/256 (by key length) on AES-NI + PCLMULQDQ, constant-time by hardware, CPUID-gated with target-attributed functions so the binary stays portable (no-AES-NI traps until phase C). Matches NIST SP 800-38D cases 4 & 16 byte-for-byte; KAT-gated in `test_crypto.c`, ASan/UBSan clean. **ARMv8 crypto-ext path deferred** (untestable on the x86-64 dev host) — folds into phase C |
|
||||||
| C — AES-GCM portability | ✅ **software fallback LANDED 2026-09-08** — portable constant-time AES (S-box via the GF(2⁸)-inverse power ladder, no tables) + bit-by-bit constant-time GHASH; same `aes_gcm_seal`/`open`, dispatched to AES-NI when present else this path. Matches NIST cases 4 & 16 byte-for-byte (test forces the software path via `wo_aes_force_software`), ASan/UBSan clean. AES-GCM is now available on any CPU (the phase-B no-AES-NI trap is retired). **ARMv8 crypto-ext hardware path still deferred** (untestable on the x86-64 dev host) — a follow-up when an ARM host exists |
|
| C — AES-GCM portability | ✅ **software fallback LANDED 2026-09-08** — portable constant-time AES (S-box via the GF(2⁸)-inverse power ladder, no tables) + bit-by-bit constant-time GHASH; same `aes_gcm_seal`/`open`, dispatched to AES-NI when present else this path. Matches NIST cases 4 & 16 byte-for-byte (test forces the software path via `wo_aes_force_software`), ASan/UBSan clean. AES-GCM is now available on any CPU (the phase-B no-AES-NI trap is retired). **ARMv8 crypto-ext hardware path still deferred** (untestable on the x86-64 dev host) — a follow-up when an ARM host exists |
|
||||||
| D — the cookie wrapper | an `encryptcookie`-equivalent on porch [2](../porch/02-randomness-and-cookies.md)'s cookie machinery: random nonce (from `random_bytes`) prepended to the ciphertext, default ChaCha |
|
| D — the cookie wrapper | **re-homed to porch 2026-09-09** (`review_pending`) — an `encryptcookie`-equivalent is pure `.wo` on porch [2](../porch/02-randomness-and-cookies.md)'s cookie machinery: random nonce (from `random_bytes`) prepended to the ciphertext, default ChaCha. It is the **consumer's** phase (as rv2 7's `/metrics` is porch's) and cannot land before porch 2 ships `random_bytes` + `SetCookie`; porch 2 names it as its follow-on. The primitives it wraps are complete here |
|
||||||
| E — the gate | RFC 8439 + NIST GCM known-answer vectors, ASan/UBSan on both paths, and a reference cross-check (`openssl enc`/a scripted peer) |
|
| E — the gate | ✅ **LANDED 2026-09-09** — RFC 8439 + NIST GCM known-answer vectors on both AES paths (`test_crypto`), ASan/UBSan clean, and the reference cross-check is **real-protocol interop, not `openssl enc`** (which cannot do AEAD modes): `just tls-server` pins `openssl s_client` to `TLS_CHACHA20_POLY1305_SHA256`, to `TLS_AES_128_GCM_SHA256`, and to openssl's default list (server must skip the unimplemented AES-256 suite) — 5/0; `just tls` reports the suite the Python/OpenSSL peer negotiated (ChaCha under its server-preference default) — 5/0 |
|
||||||
|
|
||||||
## Consumers
|
## Consumers
|
||||||
|
|
||||||
|
|
@ -139,5 +140,6 @@ Two named consumers now — TLS (rv2 9 phase A, the reason AES-GCM is in) and po
|
||||||
encrypted cookies — with database-field-at-rest a plausible third. Pure compute,
|
encrypted cookies — with database-field-at-rest a plausible third. Pure compute,
|
||||||
no actors, not exposed to the lang-41 hang. It is the **first rung of the TLS
|
no actors, not exposed to the lang-41 hang. It is the **first rung of the TLS
|
||||||
ladder**, so it gates rv2 9: nothing above TLS phase A can be built until this
|
ladder**, so it gates rv2 9: nothing above TLS phase A can be built until this
|
||||||
lands. Implementation order is A (ChaCha, unblocks the most for the least risk) →
|
lands. Implementation order was A (ChaCha, unblocks the most for the least risk) →
|
||||||
B (hardware AES-GCM) → C (software AES fallback) → D (cookie wrapper) → E (gate).
|
B (hardware AES-GCM) → C (software AES fallback) → E (gate); D (cookie wrapper)
|
||||||
|
is porch's, after porch 2. **Done 2026-09-09** as a runtime primitive.
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,7 @@ def serve():
|
||||||
except OSError: return
|
except OSError: return
|
||||||
try:
|
try:
|
||||||
s = ctx.wrap_socket(c, server_side=True)
|
s = ctx.wrap_socket(c, server_side=True)
|
||||||
|
print("SUITE", s.cipher()[0], flush=True) # which AEAD ran over the wire
|
||||||
s.recv(4096); s.sendall(b"wo-tls-ok\n"); s.close()
|
s.recv(4096); s.sendall(b"wo-tls-ok\n"); s.close()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("stub-err", e, flush=True)
|
print("stub-err", e, flush=True)
|
||||||
|
|
@ -124,7 +125,7 @@ fi
|
||||||
start_stub "$WORK/leaf.pem" "$WORK/leaf.key" || { bad "stub server did not start"; echo "tls-accept: $fail failures"; exit 1; }
|
start_stub "$WORK/leaf.pem" "$WORK/leaf.key" || { bad "stub server did not start"; echo "tls-accept: $fail failures"; exit 1; }
|
||||||
out="$(run_client "$WORK/ca.pem")"; rc=$?
|
out="$(run_client "$WORK/ca.pem")"; rc=$?
|
||||||
if [[ $rc -eq 0 && "$out" == *"wo-tls-ok"* ]]; then
|
if [[ $rc -eq 0 && "$out" == *"wo-tls-ok"* ]]; then
|
||||||
ok "handshake + trusted chain + host match + app round-trip"
|
ok "handshake + trusted chain + host match + app round-trip [$(grep -m1 '^SUITE' "$WORK/stub.log" | cut -d' ' -f2)]"
|
||||||
else
|
else
|
||||||
bad "happy path (exit $rc): $out"
|
bad "happy path (exit $rc): $out"
|
||||||
fi
|
fi
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,10 @@
|
||||||
# The runtime suite proves the server FSM offline (loopback against the client
|
# The runtime suite proves the server FSM offline (loopback against the client
|
||||||
# driver); this proves interop with a real client — `openssl s_client`
|
# driver); this proves interop with a real client — `openssl s_client`
|
||||||
# validating our hand-rolled server handshake and exchanging application data,
|
# validating our hand-rolled server handshake and exchanging application data,
|
||||||
# for both an ECDSA-P256 and an RSA server certificate. Log: /tmp/tls-server.log.
|
# for both an ECDSA-P256 and an RSA server certificate. Each probe pins the
|
||||||
|
# client's cipher suite, so both AEADs run against the reference peer over the
|
||||||
|
# wire (rv2 8 phase E's cross-check), plus one probe with openssl's default
|
||||||
|
# list, whose first entry we do not implement. Log: /tmp/tls-server.log.
|
||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
|
|
||||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
|
@ -69,7 +72,7 @@ else
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ---- 3. openssl s_client interop, per key type ---------------------------
|
# ---- 3. openssl s_client interop, per key type ---------------------------
|
||||||
probe() { # $1 = tag (ec|rsa) ; $2 = port (distinct per probe — avoids a bind race)
|
probe() { # $1 = tag (ec|rsa) ; $2 = port (distinct per probe — avoids a bind race) ; $3 = openssl -ciphersuites list
|
||||||
local p="$2"
|
local p="$2"
|
||||||
kill -KILL "$SRV_PID" 2>/dev/null
|
kill -KILL "$SRV_PID" 2>/dev/null
|
||||||
WO_DATA="$WORK/data" "$WOVM" "$WORK/srv.wob" "$p" "$WORK/$1.pem" "$WORK/$1.key" >>"$LOG" 2>&1 &
|
WO_DATA="$WORK/data" "$WOVM" "$WORK/srv.wob" "$p" "$WORK/$1.pem" "$WORK/$1.key" >>"$LOG" 2>&1 &
|
||||||
|
|
@ -81,15 +84,18 @@ probe() { # $1 = tag (ec|rsa) ; $2 = port (distinct per probe — avoids a bind
|
||||||
local out
|
local out
|
||||||
out="$({ printf 'GET / HTTP/1.0\r\n\r\n'; sleep 1; } | \
|
out="$({ printf 'GET / HTTP/1.0\r\n\r\n'; sleep 1; } | \
|
||||||
timeout 12 openssl s_client -connect "127.0.0.1:$p" -CAfile "$WORK/ca.pem" \
|
timeout 12 openssl s_client -connect "127.0.0.1:$p" -CAfile "$WORK/ca.pem" \
|
||||||
-servername localhost -tls1_3 -verify_return_error -quiet 2>/dev/null)"
|
-servername localhost -tls1_3 -ciphersuites "$3" -verify_return_error -quiet 2>/dev/null)"
|
||||||
if [[ "$out" == *"hello-wo-tls"* ]]; then
|
if [[ "$out" == *"hello-wo-tls"* ]]; then
|
||||||
ok "$1: openssl s_client validated the cert + got the reply"
|
ok "$1 [$3]: openssl s_client validated the cert + got the reply"
|
||||||
else
|
else
|
||||||
bad "$1: no reply (out: ${out:0:80})"
|
bad "$1 [$3]: no reply (out: ${out:0:80})"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
probe ec "$PORT"
|
probe ec "$PORT" TLS_CHACHA20_POLY1305_SHA256
|
||||||
probe rsa "$((PORT + 1))"
|
probe rsa "$((PORT + 1))" TLS_AES_128_GCM_SHA256
|
||||||
|
# openssl's default order: the server must skip the AES-256 suite it does not
|
||||||
|
# implement and pick from the rest
|
||||||
|
probe ec "$((PORT + 2))" TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256
|
||||||
|
|
||||||
echo "tls-server: $((pass + fail)) checks, $fail failures"
|
echo "tls-server: $((pass + fail)) checks, $fail failures"
|
||||||
[[ $fail -eq 0 ]]
|
[[ $fail -eq 0 ]]
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue