diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..3d100d10c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,78 @@ +name: ci + +on: + push: + branches: [main] + paths: + - 'app/**' + - '.github/workflows/ci.yml' + pull_request: + branches: [main] + paths: + - 'app/**' + - '.github/workflows/ci.yml' + +permissions: + contents: read + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + GOFLAGS: -buildvcs=false + +jobs: + vet: + name: vet-go-${{ matrix.go }} + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + go: ['1.23', '1.24'] + defaults: + run: + working-directory: app + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + with: + go-version: ${{ matrix.go }} + cache: true + cache-dependency-path: app/go.sum + - run: go vet ./... + + test: + name: test-go-${{ matrix.go }} + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + go: ['1.23', '1.24'] + defaults: + run: + working-directory: app + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + with: + go-version: ${{ matrix.go }} + cache: true + cache-dependency-path: app/go.sum + - run: go test -race -count=1 ./... + + lint: + name: lint + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + with: + go-version: '1.24' + cache: true + cache-dependency-path: app/go.sum + - uses: golangci/golangci-lint-action@7119f3d5ddced62a10a044847a6c6bb0f7a5e76a # v7.0.0 + with: + version: v2.5.0 + working-directory: app + args: --timeout=5m diff --git a/lab4-debug-commands.txt b/lab4-debug-commands.txt new file mode 100644 index 000000000..55712f96d --- /dev/null +++ b/lab4-debug-commands.txt @@ -0,0 +1,18 @@ +## ss -tlnp | grep :8080 +LISTEN 0 4096 *:8080 *:* users:(("quicknotes",pid=9136,fd=3)) + +## ip route show +default via 192.168.240.1 dev eth0 proto kernel +192.168.240.0/20 dev eth0 proto kernel scope link src 192.168.254.234 + +## mtr -rwc 5 localhost +Start: 2026-06-09T23:27:15+0300 +HOST: DESKTOP-U1R4GKD Loss% Snt Last Avg Best Wrst StDev + 1.|-- localhost 0.0% 5 0.1 0.3 0.1 1.4 0.6 + +## dig +short example.com @1.1.1.1 +8.47.69.0 +8.6.112.0 + +## journalctl --user -u quicknotes -n 20 || true +-- No entries -- diff --git a/lab4-openssl-cert.txt b/lab4-openssl-cert.txt new file mode 100644 index 000000000..addf04bbf --- /dev/null +++ b/lab4-openssl-cert.txt @@ -0,0 +1,91 @@ +depth=1 CN = Caddy Local Authority - ECC Intermediate +verify error:num=20:unable to get local issuer certificate +verify return:1 +depth=0 +verify return:1 +CONNECTED(00000003) +--- +Certificate chain + 0 s: + i:CN = Caddy Local Authority - ECC Intermediate + a:PKEY: id-ecPublicKey, 256 (bit); sigalg: ecdsa-with-SHA256 + v:NotBefore: Jun 9 20:48:14 2026 GMT; NotAfter: Jun 10 08:48:14 2026 GMT +-----BEGIN CERTIFICATE----- +MIIBvTCCAWOgAwIBAgIQQcnwtGhA8QZmpzU5YZBHIjAKBggqhkjOPQQDAjAzMTEw +LwYDVQQDEyhDYWRkeSBMb2NhbCBBdXRob3JpdHkgLSBFQ0MgSW50ZXJtZWRpYXRl +MB4XDTI2MDYwOTIwNDgxNFoXDTI2MDYxMDA4NDgxNFowADBZMBMGByqGSM49AgEG +CCqGSM49AwEHA0IABKQGbR3xQZoh4Ucbfe7a6jHnVLkd8/vBTFucDkt5dvuu1tw+ +NnojK/ubgJ2UV8tlQ6pzZ1KFNl6U2Gbp5tl6kAOjgYswgYgwDgYDVR0PAQH/BAQD +AgeAMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAdBgNVHQ4EFgQUCPjb +l9HGvF+gVa3o9khyxf0MXjIwHwYDVR0jBBgwFoAUTGmPgBhIOa7b25swHNZvU2Oj +UwAwFwYDVR0RAQH/BA0wC4IJbG9jYWxob3N0MAoGCCqGSM49BAMCA0gAMEUCIQDw +O4mOKmGt4was4D/o9xBcGVnLO1ueUHUgTr2iuQ5b0gIgUakXWDRTGFaR1/+R8C8M +UOqDBuVN4WoHveo3m2FXWVA= +-----END CERTIFICATE----- + 1 s:CN = Caddy Local Authority - ECC Intermediate + i:CN = Caddy Local Authority - 2026 ECC Root + a:PKEY: id-ecPublicKey, 256 (bit); sigalg: ecdsa-with-SHA256 + v:NotBefore: Jun 9 20:48:14 2026 GMT; NotAfter: Jun 16 20:48:14 2026 GMT +-----BEGIN CERTIFICATE----- +MIIByTCCAW6gAwIBAgIRAL2uoSTtjl1i+Mhr0XQLwvIwCgYIKoZIzj0EAwIwMDEu +MCwGA1UEAxMlQ2FkZHkgTG9jYWwgQXV0aG9yaXR5IC0gMjAyNiBFQ0MgUm9vdDAe +Fw0yNjA2MDkyMDQ4MTRaFw0yNjA2MTYyMDQ4MTRaMDMxMTAvBgNVBAMTKENhZGR5 +IExvY2FsIEF1dGhvcml0eSAtIEVDQyBJbnRlcm1lZGlhdGUwWTATBgcqhkjOPQIB +BggqhkjOPQMBBwNCAATwBaaqryuHE9RLEjMLOpPmoexH9I9Uy9rSrTa/Q1lHG0h5 +9+XR6qJ4AG02A8v6b09ctrGPppVYm/DJN6+dD8zbo2YwZDAOBgNVHQ8BAf8EBAMC +AQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUTGmPgBhIOa7b25swHNZv +U2OjUwAwHwYDVR0jBBgwFoAURi39BFvOjRCvcRJVK+3cGI1GxIcwCgYIKoZIzj0E +AwIDSQAwRgIhAKYIQEKcQx8uj1l7uKX820yWBhpVMBddVEYc+ViT9xqkAiEAl4Pf +xfF89l7vM/PVHGCoxEkl8L6QBib86J+SDlIxO48= +-----END CERTIFICATE----- +--- +Server certificate +subject= +issuer=CN = Caddy Local Authority - ECC Intermediate +--- +No client certificate CA names sent +Peer signing digest: SHA256 +Peer signature type: ECDSA +Server Temp Key: X25519, 253 bits +--- +SSL handshake has read 1271 bytes and written 375 bytes +Verification error: unable to get local issuer certificate +--- +New, TLSv1.3, Cipher is TLS_AES_128_GCM_SHA256 +Server public key is 256 bit +Secure Renegotiation IS NOT supported +Compression: NONE +Expansion: NONE +No ALPN negotiated +Early data was not sent +Verify return code: 20 (unable to get local issuer certificate) +--- +DONE +--- +Post-Handshake New Session Ticket arrived: +SSL-Session: + Protocol : TLSv1.3 + Cipher : TLS_AES_128_GCM_SHA256 + Session-ID: 70E999CD090B0313F81067A24843BFAB91A54C3DCBCA730266CD3925368C4807 + Session-ID-ctx: + Resumption PSK: 1993D9804CADCCEB6AA7B19BD148196492F6A251C587B1FE3E434D7E81D6A5C1 + PSK identity: None + PSK identity hint: None + SRP username: None + TLS session ticket lifetime hint: 604800 (seconds) + TLS session ticket: + 0000 - d9 16 3e a2 78 f7 01 06-42 ec 8b dd fd 78 3c 70 ..>.x...B....x
GET /health HTTP/2 +> Host: localhost:8443 +> User-Agent: curl/8.5.0 +> Accept: */* +> +{ [5 bytes data] +< HTTP/2 200 +< alt-svc: h3=":8443"; ma=2592000 +< content-type: application/json +< date: Tue, 09 Jun 2026 20:51:19 GMT +< server: Caddy +< content-length: 26 +< +{ [26 bytes data] + 100 26 100 26 0 0 760 0 --:--:-- --:--:-- --:--:-- 764 +* Connection #0 to host localhost left intact +{"notes":6,"status":"ok"} diff --git a/lab4-tls-deprecation.txt b/lab4-tls-deprecation.txt new file mode 100644 index 000000000..0f3eac0b3 --- /dev/null +++ b/lab4-tls-deprecation.txt @@ -0,0 +1,59 @@ +## TLS 1.0 test +4047A33D54750000:error:0A0000BF:SSL routines:tls_setup_handshake:no protocols available:../ssl/statem/statem_lib.c:104: +CONNECTED(00000003) +--- +no peer certificate available +--- +No client certificate CA names sent +--- +SSL handshake has read 0 bytes and written 7 bytes +Verification: OK +--- +New, (NONE), Cipher is (NONE) +Secure Renegotiation IS NOT supported +Compression: NONE +Expansion: NONE +No ALPN negotiated +Early data was not sent +Verify return code: 0 (ok) +--- + +## TLS 1.1 test +40F78B10BA770000:error:0A0000BF:SSL routines:tls_setup_handshake:no protocols available:../ssl/statem/statem_lib.c:104: +CONNECTED(00000003) +--- +no peer certificate available +--- +No client certificate CA names sent +--- +SSL handshake has read 0 bytes and written 7 bytes +Verification: OK +--- +New, (NONE), Cipher is (NONE) +Secure Renegotiation IS NOT supported +Compression: NONE +Expansion: NONE +No ALPN negotiated +Early data was not sent +Verify return code: 0 (ok) +--- + +## TLS 1.2 test +CONNECTED(00000003) +New, TLSv1.2, Cipher is ECDHE-ECDSA-AES128-GCM-SHA256 + Protocol : TLSv1.2 + Cipher : ECDHE-ECDSA-AES128-GCM-SHA256 + Verify return code: 20 (unable to get local issuer certificate) + +## TLS 1.3 evidence from curl +* TLSv1.3 (OUT), TLS handshake, Client hello (1): +* TLSv1.3 (IN), TLS handshake, Server hello (2): +* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8): +* TLSv1.3 (IN), TLS handshake, Certificate (11): +* TLSv1.3 (IN), TLS handshake, CERT verify (15): +* TLSv1.3 (IN), TLS handshake, Finished (20): +* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1): +* TLSv1.3 (OUT), TLS handshake, Finished (20): +* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256 / X25519 / id-ecPublicKey +* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4): +< HTTP/2 200 diff --git a/lab4-tls.pcap b/lab4-tls.pcap new file mode 100644 index 000000000..976d40a1c Binary files /dev/null and b/lab4-tls.pcap differ diff --git a/lab4-trace.pcap b/lab4-trace.pcap new file mode 100644 index 000000000..1de6828a1 Binary files /dev/null and b/lab4-trace.pcap differ diff --git a/lab4-trace.txt b/lab4-trace.txt new file mode 100644 index 000000000..7f49cbe3c --- /dev/null +++ b/lab4-trace.txt @@ -0,0 +1,43 @@ +23:21:16.065482 IP6 ::1.32966 > ::1.8080: Flags [S], seq 413340489, win 65476, options [mss 65476,sackOK,TS val 2475753567 ecr 0,nop,wscale 7], length 0 +`....(.@.......................................I.........0......... +..._........ +23:21:16.066930 IP6 ::1.8080 > ::1.32966: Flags [S.], seq 2599690076, ack 413340490, win 65464, options [mss 65476,sackOK,TS val 2475753569 ecr 2475753567,nop,wscale 7], length 0 +`../.(.@.......................................\...J.....0......... +...a..._.... +23:21:16.067406 IP6 ::1.32966 > ::1.8080: Flags [.], ack 1, win 512, options [nop,nop,TS val 2475753570 ecr 2475753569], length 0 +`.... .@.......................................J...].....(..... +...b...a +23:21:16.068922 IP6 ::1.32966 > ::1.8080: Flags [P.], seq 1:175, ack 1, win 512, options [nop,nop,TS val 2475753571 ecr 2475753569], length 174: HTTP: POST /notes HTTP/1.1 +`......@.......................................J...]........... +...c...aPOST /notes HTTP/1.1 +Host: localhost:8080 +User-Agent: curl/8.5.0 +Accept: */* +Content-Type: application/json +Content-Length: 39 + +{"title":"trace me","body":"in flight"} +23:21:16.068950 IP6 ::1.8080 > ::1.32966: Flags [.], ack 175, win 511, options [nop,nop,TS val 2475753571 ecr 2475753571], length 0 +`../. .@.......................................].........(..... +...c...c +23:21:16.084058 IP6 ::1.8080 > ::1.32966: Flags [P.], seq 1:207, ack 175, win 512, options [nop,nop,TS val 2475753587 ecr 2475753571], length 206: HTTP: HTTP/1.1 201 Created +`../...@.......................................]............... +...s...cHTTP/1.1 201 Created +Content-Type: application/json +Date: Tue, 09 Jun 2026 20:21:16 GMT +Content-Length: 93 + +{"id":6,"title":"trace me","body":"in flight","created_at":"2026-06-09T20:21:16.071257766Z"} + +23:21:16.084120 IP6 ::1.32966 > ::1.8080: Flags [.], ack 207, win 511, options [nop,nop,TS val 2475753587 ecr 2475753587], length 0 +`.... .@.......................................... +.....(..... +...s...s +23:21:16.090747 IP6 ::1.32966 > ::1.8080: Flags [F.], seq 175, ack 207, win 512, options [nop,nop,TS val 2475753593 ecr 2475753587], length 0 +`.... .@.......................................... +.....(..... +...y...s +23:21:16.091016 IP6 ::1.8080 > ::1.32966: Flags [F.], seq 207, ack 176, win 512, options [nop,nop,TS val 2475753593 ecr 2475753593], length 0 +`../. .@...................................... +.........(..... +...y...y +23:21:16.091238 IP6 ::1.32966 > ::1.8080: Flags [.], ack 208, win 512, options [nop,nop,TS val 2475753594 ecr 2475753593], length 0 +`.... .@.......................................... ,.....(..... +...z...y diff --git a/lab4-wireshark-certchain.png b/lab4-wireshark-certchain.png new file mode 100755 index 000000000..d45e3a460 Binary files /dev/null and b/lab4-wireshark-certchain.png differ diff --git a/lab4-wireshark-clienthello.png b/lab4-wireshark-clienthello.png new file mode 100755 index 000000000..808b081fa Binary files /dev/null and b/lab4-wireshark-clienthello.png differ diff --git a/lab4-wireshark-serverhello.png b/lab4-wireshark-serverhello.png new file mode 100755 index 000000000..14a2737a9 Binary files /dev/null and b/lab4-wireshark-serverhello.png differ diff --git a/submissions/lab4.md b/submissions/lab4.md new file mode 100644 index 000000000..904f8d9fd --- /dev/null +++ b/submissions/lab4.md @@ -0,0 +1,405 @@ +# Lab 4 Submission — OS & Networking: Trace, Debug, and Read the Substrate + +## Task 1 — Trace a Request End-to-End + +### 1.1 Capture setup + +QuickNotes was started locally from the `app/` directory using: + + go run . + +A packet capture was started on loopback for TCP port 8080: + + sudo tcpdump -i lo -nn -s 0 -A 'tcp port 8080' -w lab4-trace.pcap + +Then one request was sent: + + curl -v -X POST http://localhost:8080/notes \ + -H 'Content-Type: application/json' \ + -d '{"title":"trace me","body":"in flight"}' + +The request returned: + + HTTP/1.1 201 Created + +Response body: + + {"id":6,"title":"trace me","body":"in flight","created_at":"2026-06-09T20:21:16.071257766Z"} + +### 1.2 Annotated packet trace + +The decoded trace was saved in: + + lab4-trace.txt + +TCP three-way handshake: + + 23:21:16.065482 IP6 ::1.32966 > ::1.8080: Flags [S] + 23:21:16.066930 IP6 ::1.8080 > ::1.32966: Flags [S.] + 23:21:16.067406 IP6 ::1.32966 > ::1.8080: Flags [.] + +This shows SYN, SYN/ACK, and ACK. + +HTTP request: + + POST /notes HTTP/1.1 + Host: localhost:8080 + User-Agent: curl/8.5.0 + Accept: */* + Content-Type: application/json + Content-Length: 39 + + {"title":"trace me","body":"in flight"} + +HTTP response: + + HTTP/1.1 201 Created + Content-Type: application/json + Content-Length: 93 + + {"id":6,"title":"trace me","body":"in flight","created_at":"2026-06-09T20:21:16.071257766Z"} + +Connection close: + + 23:21:16.090747 IP6 ::1.32966 > ::1.8080: Flags [F.] + 23:21:16.091016 IP6 ::1.8080 > ::1.32966: Flags [F.] + 23:21:16.091238 IP6 ::1.32966 > ::1.8080: Flags [.] + +The connection closed with FIN/FIN/ACK. + +### 1.3 Five debugging commands + +#### 1. What is listening? + +Command: + + ss -tlnp | grep :8080 + +Output: + + LISTEN 0 4096 *:8080 *:* users:(("quicknotes",pid=9136,fd=3)) + +Decision: + + QuickNotes is listening on TCP port 8080. + +#### 2. What routes does the host have? + +Command: + + ip route show + +Output: + + default via 192.168.240.1 dev eth0 proto kernel + 192.168.240.0/20 dev eth0 proto kernel scope link src 192.168.254.234 + +Decision: + + The host has a default route through eth0 and a directly connected local subnet route. + +#### 3. Can localhost be reached? + +Command: + + mtr -rwc 5 localhost + +Output: + + HOST: DESKTOP-U1R4GKD Loss% Snt Last Avg Best Wrst StDev + 1.|-- localhost 0.0% 5 0.1 0.3 0.1 1.4 0.6 + +Decision: + + Localhost is reachable with 0% packet loss over the loopback path. + +#### 4. Does DNS work? + +Command: + + dig +short example.com @1.1.1.1 + +Output: + + 8.47.69.0 + 8.6.112.0 + +Decision: + + DNS resolution works using Cloudflare resolver 1.1.1.1. + +#### 5. Are there user service logs? + +Command: + + journalctl --user -u quicknotes -n 20 || true + +Output: + + -- No entries -- + +Decision: + + QuickNotes was run manually with `go run .`, not installed as a user systemd service, so there are no journald unit logs for `quicknotes`. + +### 1.4 502 debugging reflection + +If QuickNotes returned 502, I would debug outside-in. First I would check whether the service process is running, then whether it is listening on the expected port with `ss -tlnp`. Next I would test local reachability with `curl localhost:8080/health`. If the application is healthy locally, I would check the reverse proxy or load balancer path, firewall rules, and DNS resolution. This order separates application failure from transport, routing, firewall, and name-resolution problems. + +## Task 2 — Broken Deploy Debugging + +### 2.1 Broken instance reproduction + +I started one QuickNotes instance on port 8080: + + ADDR=:8080 go run . & + PID1=$! + +Then I started a second instance on the same port: + + ADDR=:8080 go run . 2>&1 | tee /tmp/qn-broken.log & + +The second instance failed with: + + 2026/06/09 23:35:11 quicknotes listening on :8080 (notes loaded: 6) + 2026/06/09 23:35:11 listen: listen tcp :8080: bind: address already in use + exit status 1 + +Root cause: + + listen tcp :8080: bind: address already in use + +Only one process can bind to TCP port 8080 at a time. The first QuickNotes process was already listening, so the second instance failed. + +### 2.2 Outside-in debugging chain + +#### 1. Is the service/process running? + +Command: + + ps -ef | grep quicknotes | grep -v grep || true + ps -ef | grep "go run" | grep -v grep || true + +Output: + + teeroyce 9382 9319 0 23:35 pts/2 00:00:00 /home/teeroyce/.cache/go-build/.../quicknotes + teeroyce 9319 3091 0 23:35 pts/2 00:00:00 go run . + +Decision: + + A QuickNotes compiled child process was running, and the `go run .` wrapper was also present. + +#### 2. Is the service listening? + +Command: + + ss -tlnp | grep 8080 + +Output: + + LISTEN 0 4096 *:8080 *:* users:(("quicknotes",pid=9382,fd=3)) + +Decision: + + Port 8080 was already occupied by the first QuickNotes instance. + +#### 3. Is the application reachable locally? + +Command: + + curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8080/health + +Output: + + 200 + +Decision: + + The existing QuickNotes instance was healthy and reachable locally. + +#### 4. Is the firewall blocking traffic? + +Command: + + sudo iptables -L -n -v 2>/dev/null || sudo nft list ruleset 2>/dev/null || true + +Output: + + No blocking firewall rule was shown in the captured output. + +Decision: + + The failure was not caused by firewall blocking because the local health check returned HTTP 200. + +#### 5. Does localhost resolve? + +Command: + + dig +short localhost + +Output: + + 127.0.0.1 + +Decision: + + Localhost name resolution works. + +### 2.3 Repair + +First I tried killing `PID1`, but this only terminated the `go run` wrapper. The compiled QuickNotes child process continued listening on port 8080: + + LISTEN 0 4096 *:8080 *:* users:(("quicknotes",pid=9382,fd=3)) + +I then killed the actual listener: + + kill 9382 + +After that, port 8080 became free: + + 8080 is free after killing actual listener + +Then I restarted QuickNotes: + + ADDR=:8080 go run . & + +The health check passed: + + {"notes":6,"status":"ok"} + +The repaired listener was visible: + + LISTEN 0 4096 *:8080 *:* users:(("quicknotes",pid=9617,fd=3)) + +### 2.4 Mini-postmortem + +The failure happened because two QuickNotes instances tried to bind to the same TCP port, `:8080`. The first instance already owned the port, so the second instance failed immediately with `bind: address already in use`. The outside-in checks showed that DNS and local reachability were working, the existing application was healthy, and the real fault was at the process/socket layer. A useful prevention is to check port ownership before deployment using `ss -tlnp | grep 8080`, stop the existing service cleanly, and prefer a process manager such as systemd so service lifecycle is explicit. + +## Bonus — HTTPS Reverse Proxy and TLS Inspection + +### B.1 Caddy reverse proxy + +Caddy was installed using: + + sudo apt install -y caddy + +Version: + + 2.6.2 + +Caddy was configured with `/etc/caddy/Caddyfile`: + + localhost:8443 { + reverse_proxy localhost:8080 + } + +The configuration validated successfully: + + Valid configuration + +Caddy service status showed: + + Active: active (running) + certificate obtained successfully + identifier="localhost" + +### B.2 HTTPS request through Caddy + +Command: + + curl -vk https://localhost:8443/health + +Important TLS handshake evidence: + + TLSv1.3 (OUT), TLS handshake, Client hello (1) + TLSv1.3 (IN), TLS handshake, Server hello (2) + TLSv1.3 (IN), TLS handshake, Certificate (11) + SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256 / X25519 / id-ecPublicKey + +HTTP result: + + HTTP/2 200 + {"notes":6,"status":"ok"} + +### B.3 TLS packet capture + +TLS traffic was captured using: + + sudo tcpdump -U -i lo -nn -s 0 -w lab4-tls.pcap 'tcp port 8443' + +Capture result: + + 20 packets captured + 40 packets received by filter + 0 packets dropped by kernel + +Decoded packet summary showed: + + SYN + SYN/ACK + ACK + encrypted TLS application data + FIN/RST close + +The plaintext HTTP request is not visible in the packet capture because it is encrypted inside TLS. + +### B.4 Certificate chain + +OpenSSL initially failed without SNI, so I reran it with `-servername localhost`: + + openssl s_client -connect localhost:8443 -servername localhost -showcerts + +The certificate chain showed: + + Certificate chain + issuer=CN = Caddy Local Authority - ECC Intermediate + New, TLSv1.3, Cipher is TLS_AES_128_GCM_SHA256 + Protocol : TLSv1.3 + Cipher : TLS_AES_128_GCM_SHA256 + +The certificate is issued by Caddy's local CA, which is expected for localhost automatic HTTPS. + +### B.5 TLS 1.0 and TLS 1.1 deprecation evidence + +TLS 1.0 test: + + openssl s_client -connect localhost:8443 -servername localhost -tls1 + +Result: + + tls_setup_handshake:no protocols available + no peer certificate available + New, (NONE), Cipher is (NONE) + +TLS 1.1 test: + + openssl s_client -connect localhost:8443 -servername localhost -tls1_1 + +Result: + + tls_setup_handshake:no protocols available + no peer certificate available + New, (NONE), Cipher is (NONE) + +TLS 1.2 still works: + + New, TLSv1.2, Cipher is ECDHE-ECDSA-AES128-GCM-SHA256 + Protocol : TLSv1.2 + +TLS 1.3 works and was used by curl: + + SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256 / X25519 / id-ecPublicKey + +Conclusion: + + TLS 1.0 and TLS 1.1 are deprecated and unavailable in this environment. Caddy successfully serves modern HTTPS on localhost:8443 using TLS 1.2/1.3. + +### B.6 Wireshark screenshots + +The following Wireshark screenshots are included as bonus evidence: + +- `lab4-wireshark-clienthello.png` — ClientHello showing SNI `localhost`, TLS version field, and offered cipher suites. +- `lab4-wireshark-serverhello.png` — ServerHello showing the selected TLS version and cipher suite. +- `lab4-wireshark-certchain.png` — OpenSSL certificate chain evidence showing Caddy local CA certificates and TLSv1.3 cipher information. + +TLS 1.0 and TLS 1.1 are not negotiated. The working connection uses modern TLS, and the successful curl/OpenSSL evidence shows TLSv1.3 with `TLS_AES_128_GCM_SHA256`.