Programação Distribuída com Redes usando Linux e Python

Socket, TCP/UDP, HTTP e TLS em Python: o que fazer, código, comando de teste, evidência de screenshot e explicação curta para fechar a resposta com segurança.

Por Fábio Linhares • Instituto Infnet

BASE
Contrato de entrega e método único para os 16 exercícios
Organizar execução e escrita para responder o TP3 com consistência técnica, sem lacunas entre implementação, teste e prova.

Este material foi estruturado para evitar o padrão que mais derruba nota em disciplinas práticas: código que roda, mas sem prova clara e sem explicação objetiva. A estratégia aqui é padronizar cada exercício em cinco peças fixas. Com isso, você transforma uma sequência longa (socket básico, TCP, UDP, HTTP manual, HTTP com framework e TLS) em um roteiro único e repetível.

O objetivo não é "escrever muito". O objetivo é responder com precisão, em formato auditável. Quando você sempre apresenta implementação, teste e evidência na mesma ordem, o avaliador consegue validar rápido e você reduz risco de esquecer partes essenciais.

Peça obrigatória O que entra
1) O que foi feito Resumo em 1-3 frases do objetivo técnico do exercício.
2) Código Arquivo .py com implementação mínima funcional.
3) Como foi testado Comandos exatos executados no terminal.
4) Evidência para PDF Recorte curto de screenshot com sinal técnico relevante.
5) O que a evidência prova 1 frase de conclusão + 2-4 linhas quando o enunciado pedir explicação.

Pré-voo (2 minutos)

python3 --version
command -v nc
command -v curl
command -v openssl
command -v ss

ss -tlnp | egrep ':(5000|6000|8080|8443)\b' || true
mkdir -p tp3_socket && cd tp3_socket
Nota de permissão: se ss -tlnp/ss -ulnp não mostrar processo/PID, rode com sudo. O LISTEN já prova porta aberta; o PID prova o "dono" do socket.

Checklist transversal (vale para todo exercício com servidor)

  • Provar LISTEN quando houver serviço.
  • Provar funcionalidade de ponta a ponta com cliente.
  • Capturar status/headers/bytes quando o protocolo for HTTP.
  • Anexar evidência curta e legível, sem dump gigante.
  • Fechar com uma frase objetiva do tipo "isso prova que...".

Glossário mínimo (para explicar com propriedade)

Termo Leitura prática no TP3
Endpoint Par IP:porta (ex.: 127.0.0.1:8080).
TCP (stream) recv() lê bytes que chegaram agora; não existe "mensagem" implícita.
UDP (datagrama) recvfrom() opera por datagrama, sem garantia de entrega/ordem fim a fim.
CRLF em HTTP Headers terminam em \r\n\r\n; depois disso começa o corpo.
Content-Length Quantidade de bytes do corpo; em POST manual, você precisa ler exatamente esse tamanho.
TLS Canal criptografado sob HTTP; HTTPS = HTTP sobre TLS.
Se você mantiver esse formato em todos os itens, a página vira material de resposta completo, não só rascunho de código.
EX.01
Conceitos base: famílias, tipos, bind/listen/accept e porta 0
Responder a base conceitual do módulo socket com mini-prova executável.
O que a questão pede: explicar AF_INET/AF_INET6, STREAM/DGRAM, ciclo de servidor TCP e por que porta 0 gera alocação efêmera.

Conceito cobrado

  • AF_INET e AF_INET6 definem formato de endereço (IPv4/IPv6).
  • SOCK_STREAM usa TCP (confiável e ordenado); SOCK_DGRAM usa UDP (sem garantia fim a fim).
  • Servidor TCP clássico: bind -> listen -> accept.
  • Porta 0 delega ao SO a escolha de uma porta livre.
# ex1_porta0.py
import socket

def main():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind(("0.0.0.0", 0))
    print("Porta escolhida:", s.getsockname()[1])
    s.close()

if __name__ == "__main__":
    main()

Como testar

python3 ex1_porta0.py
Evidência: screenshot do comando e da porta escolhida automaticamente.
O que isso prova: porta 0 não é serviço fixo; é mecanismo de alocação efêmera gerenciada pelo SO.
EX.02
Resolução de endereço com getaddrinfo para TCP
Mostrar saída real para example.com:80 e interpretar cada campo da tupla retornada.
O que a questão pede: executar getaddrinfo com AF_UNSPEC + SOCK_STREAM e explicar family, type, proto, canonname e sockaddr.
# ex2_getaddrinfo.py
import socket

def main():
    host = "example.com"
    port = 80
    infos = socket.getaddrinfo(host, port, family=socket.AF_UNSPEC, type=socket.SOCK_STREAM)

    print(f"Total de resultados: {len(infos)}")
    for i, (family, socktype, proto, canonname, sockaddr) in enumerate(infos, 1):
        fam = "AF_INET" if family == socket.AF_INET else "AF_INET6" if family == socket.AF_INET6 else str(family)
        st = "SOCK_STREAM" if socktype == socket.SOCK_STREAM else str(socktype)
        print(f"[{i}] family={fam} socktype={st} proto={proto} canonname={canonname!r} sockaddr={sockaddr}")

if __name__ == "__main__":
    main()

Como testar

python3 ex2_getaddrinfo.py

Como explicar a saída

  • family: IPv4 ou IPv6.
  • socktype: tipo de transporte (aqui, TCP).
  • proto: número de protocolo associado.
  • canonname: nome canônico (pode vir vazio).
  • sockaddr: tupla pronta para connect.
Evidência: screenshot da saída real completa no seu ambiente.
EX.03
Bloqueante, timeout e tentativa de conexão controlada
Configurar timeout de 2 segundos e tratar o caso de expiração com saída textual previsível.
Como pensar: timeout não "garante erro timeout em toda rede", mas impõe teto de espera quando o fluxo não progride.
# ex3_timeout.py
import socket

def main():
    addr = ("192.0.2.1", 12345)  # TEST-NET-1
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.settimeout(2.0)
    try:
        s.connect(addr)
        print("conectou (inesperado)")
    except socket.timeout:
        print("timeout")
    finally:
        s.close()

if __name__ == "__main__":
    main()

Como testar

python3 ex3_timeout.py
Se ocorrer "Network is unreachable", você recebeu falha de rota imediata antes de consumir o timeout. Isso não invalida o conceito.
Evidência esperada: screenshot com saída timeout quando o ambiente permitir.
EX.04
Endianness: htons e struct.pack("!H")
Demonstrar ordem de bytes de rede (big-endian) e validar ida e volta de serialização.
# ex4_endian.py
import socket
import struct
import sys

def main():
    port = 8080
    print("Host byteorder:", sys.byteorder)
    print("8080 (host int):", port)
    print("htons(8080):", socket.htons(port))

    packed = struct.pack("!H", port)
    print("struct.pack('!H',8080) bytes:", packed)
    print("unpack ->", struct.unpack("!H", packed)[0])

if __name__ == "__main__":
    main()

Como testar

python3 ex4_endian.py
Explicação curta pronta: htons converte inteiro de host order para network order; o prefixo ! no struct fixa big-endian.
Evidência: screenshot do comando e da saída com byteorder, conversão e unpack.
EX.05
Servidor TCP echo sequencial em 0.0.0.0:5000
Implementar listener TCP simples que recebe até 1024 bytes e devolve os mesmos dados ao cliente.
# ex5_tcp_echo_server.py
import socket

HOST = "0.0.0.0"
PORT = 5000

def main():
    srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    srv.bind((HOST, PORT))
    srv.listen(50)
    print(f"TCP echo server em {HOST}:{PORT} (Ctrl+C para sair)")

    try:
        while True:
            conn, addr = srv.accept()
            print("Cliente conectado:", addr)
            try:
                data = conn.recv(1024)
                if data:
                    conn.sendall(data)
            finally:
                conn.close()
                print("Cliente finalizado:", addr)
    except KeyboardInterrupt:
        print("Encerrando por KeyboardInterrupt")
    finally:
        srv.close()

if __name__ == "__main__":
    main()

Como testar (2 terminais)

# Terminal 1
python3 ex5_tcp_echo_server.py
ss -tlnp | grep ':5000\b' || true

# Terminal 2
nc 127.0.0.1 5000
# digite uma linha e Enter
Nota de permissão: se o PID não aparecer no ss -tlnp, execute com sudo para provar também o processo dono da porta.
  • Screenshot do servidor com "Cliente conectado".
  • Screenshot do cliente nc mostrando eco do texto.
  • Opcional: screenshot de LISTEN na porta 5000.
Isso prova escuta TCP ativa e troca de bytes fim a fim.
EX.06
Cliente TCP com retry (3 tentativas) e pausa de 1 segundo
Implementar cliente resiliente com timeout, retentativa limitada e mensagem PING.
# ex6_tcp_client_retry.py
import socket
import time
import sys

HOST = sys.argv[1] if len(sys.argv) > 1 else "127.0.0.1"
PORT = 5000
MSG = b"PING\n"

def main():
    attempts = 3
    for i in range(1, attempts + 1):
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.settimeout(2.0)
        try:
            s.connect((HOST, PORT))
            s.sendall(MSG)
            data = s.recv(1024)
            print(data.decode("utf-8", errors="replace"), end="")
            return
        except (OSError, socket.timeout) as e:
            print(f"tentativa {i}/{attempts} falhou: {e}")
            if i < attempts:
                time.sleep(1.0)
        finally:
            s.close()
    print("desistindo após 3 tentativas")

if __name__ == "__main__":
    main()

Como testar

# com o servidor do Ex.05 rodando
python3 ex6_tcp_client_retry.py 127.0.0.1
Evidência: screenshot do cliente imprimindo PING (eco recebido).
O que isso prova: timeout + retentativa controlada + comunicação bem-sucedida quando o serviço está disponível.
EX.07
Echo UDP em 0.0.0.0:6000 com cliente de três mensagens
Mostrar round-trip UDP e explicar, de forma curta, perda e reordenação de datagramas.
# ex7_udp_server.py
import socket

HOST = "0.0.0.0"
PORT = 6000

def main():
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    s.bind((HOST, PORT))
    print(f"UDP server em {HOST}:{PORT}")
    while True:
        data, addr = s.recvfrom(2048)
        s.sendto(b"OK:" + data, addr)

if __name__ == "__main__":
    main()
# ex7_udp_client.py
import socket

HOST = "127.0.0.1"
PORT = 6000

def main():
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    for m in ["um", "dois", "tres"]:
        s.sendto(m.encode("utf-8"), (HOST, PORT))
        data, _ = s.recvfrom(2048)
        print(data.decode("utf-8", errors="replace"))
    s.close()

if __name__ == "__main__":
    main()

Como testar

# Terminal 1
python3 ex7_udp_server.py
ss -ulnp | grep ':6000\b' || true

# Terminal 2
python3 ex7_udp_client.py
Nota de permissão: se o PID não aparecer no ss -ulnp, execute com sudo para mapear o processo dono do socket UDP.

Explicação curta pronta

  • UDP pode perder pacotes porque não existe retransmissão garantida fim a fim.
  • UDP não garante ordem porque cada datagrama é independente no caminho de rede.
Evidência: screenshot do cliente com três linhas: OK:um, OK:dois, OK:tres.
EX.08
Servidor TCP echo com threads (uma conexão por thread)
Demonstrar atendimento concorrente com duas sessões de cliente abertas ao mesmo tempo.
# ex8_tcp_echo_threaded.py
import socket
import threading

HOST = "0.0.0.0"
PORT = 5000

def handle_client(conn, addr):
    try:
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)
    finally:
        conn.close()
        print("Cliente saiu:", addr)

def main():
    srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    srv.bind((HOST, PORT))
    srv.listen(50)
    print(f"TCP echo threaded em {HOST}:{PORT} (Ctrl+C para sair)")

    try:
        while True:
            conn, addr = srv.accept()
            print("Cliente conectou:", addr)
            t = threading.Thread(target=handle_client, args=(conn, addr), daemon=True)
            t.start()
    except KeyboardInterrupt:
        print("Encerrando")
    finally:
        srv.close()

if __name__ == "__main__":
    main()

Como testar (3 terminais)

# Terminal 1
python3 ex8_tcp_echo_threaded.py
ss -tlnp | grep ':5000\b' || true

# Terminal 2
nc 127.0.0.1 5000

# Terminal 3
nc 127.0.0.1 5000
Nota de permissão: sem sudo, algumas distros ocultam PID/processo no ss -tlnp.
Evidência esperada: servidor mostrando duas conexões e ambos os clientes recebendo eco.
Conclusão curta: o loop principal continua aceitando conexões enquanto as threads atendem sessões já estabelecidas.
EX.09
HTTP manual em :8080 com resposta hello world!
Montar servidor HTTP mínimo com status, headers corretos e fechamento de conexão.
Conceito cobrado: HTTP é texto de cabeçalho + corpo em bytes; o fim dos headers é \r\n\r\n; Content-Length precisa bater com o body.
Só um servidor por porta por vez: antes de iniciar o próximo exercício HTTP em :8080, encerre o servidor anterior para evitar Address already in use.
# ex9_http_hello.py
import socket

HOST = "0.0.0.0"
PORT = 8080

def recv_until(conn, marker=b"\r\n\r\n", limit=8192):
    data = b""
    while marker not in data:
        chunk = conn.recv(1024)
        if not chunk:
            break
        data += chunk
        if len(data) > limit:
            break
    return data

def main():
    srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    srv.bind((HOST, PORT))
    srv.listen(50)
    print(f"HTTP manual em {HOST}:{PORT}")

    body = b"hello world!"
    headers = (
        b"HTTP/1.1 200 OK\r\n"
        b"Content-Type: text/plain; charset=utf-8\r\n"
        b"Content-Length: 12\r\n"
        b"Connection: close\r\n"
        b"\r\n"
    )
    resp = headers + body

    while True:
        conn, _ = srv.accept()
        try:
            _ = recv_until(conn)
            conn.sendall(resp)
        finally:
            conn.close()

if __name__ == "__main__":
    main()

Como testar

python3 ex9_http_hello.py
ss -tlnp | grep ':8080\b' || true
curl -v http://127.0.0.1:8080/
Nota de permissão: se o ss -tlnp não mostrar PID, rode com sudo para evidenciar o processo do listener.
Evidência: screenshot do curl -v exibindo status, headers e body.
Para o TP, não é necessário implementar keep-alive, chunked transfer ou pipeline de requisições.
EX.10
Servir index.html no / e responder 404 fora da rota
Implementar roteamento mínimo manual com Content-Length correto em respostas 200/404.
# preparo do arquivo
printf '<h1>OK</h1>\n' > index.html
# ex10_http_files_404.py
import socket
import os

HOST = "0.0.0.0"
PORT = 8080

def recv_until(conn, marker=b"\r\n\r\n", limit=16384):
    data = b""
    while marker not in data:
        chunk = conn.recv(1024)
        if not chunk:
            break
        data += chunk
        if len(data) > limit:
            break
    return data

def parse_request_line(req_bytes):
    line = req_bytes.split(b"\r\n", 1)[0].decode("iso-8859-1", errors="replace")
    parts = line.split()
    if len(parts) >= 2:
        return parts[0], parts[1]
    return None, None

def send_response(conn, status_line, content_type, body_bytes):
    hdr = (
        f"HTTP/1.1 {status_line}\r\n"
        f"Content-Type: {content_type}\r\n"
        f"Content-Length: {len(body_bytes)}\r\n"
        f"Connection: close\r\n"
        f"\r\n"
    ).encode("utf-8")
    conn.sendall(hdr + body_bytes)

def main():
    srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    srv.bind((HOST, PORT))
    srv.listen(50)
    print(f"HTTP manual em {HOST}:{PORT}")

    while True:
        conn, _ = srv.accept()
        try:
            req = recv_until(conn)
            method, path = parse_request_line(req)

            if method != "GET":
                send_response(conn, "405 Method Not Allowed", "text/plain; charset=utf-8", b"Method Not Allowed")
                continue

            if path == "/":
                if os.path.exists("index.html"):
                    body = open("index.html", "rb").read()
                    send_response(conn, "200 OK", "text/html; charset=utf-8", body)
                else:
                    send_response(conn, "500 Internal Server Error", "text/plain; charset=utf-8", b"index.html missing")
            else:
                send_response(conn, "404 Not Found", "text/plain; charset=utf-8", b"Not Found")
        finally:
            conn.close()

if __name__ == "__main__":
    main()

Como testar

python3 ex10_http_files_404.py
curl -i http://127.0.0.1:8080/
curl -i http://127.0.0.1:8080/naoexiste
Evidência: dois screenshots (200 no / e 404 fora da rota), ambos com Content-Length coerente.
EX.11
Rotas manuais: GET /status e POST /echo (até 10 KiB)
Ler headers até delimitador, interpretar Content-Length e consumir corpo exatamente no tamanho informado.
# ex11_http_routes.py
import datetime
import json
import socket

HOST = "0.0.0.0"
PORT = 8080
MAX_BODY = 10 * 1024

def recv_headers(conn, marker=b"\r\n\r\n", limit=65536):
    data = b""
    while marker not in data:
        chunk = conn.recv(1024)
        if not chunk:
            break
        data += chunk
        if len(data) > limit:
            break
    parts = data.split(marker, 1)
    if len(parts) == 2:
        return parts[0], parts[1]
    return data, b""

def parse_request(head_bytes):
    lines = head_bytes.decode("iso-8859-1", errors="replace").split("\r\n")
    request_line = lines[0] if lines else ""
    pieces = request_line.split()
    method = pieces[0] if len(pieces) >= 1 else ""
    path = pieces[1] if len(pieces) >= 2 else ""
    headers = {}
    for line in lines[1:]:
        if ":" in line:
            k, v = line.split(":", 1)
            headers[k.strip().lower()] = v.strip()
    return method, path, headers

def read_body(conn, body_start, content_length):
    body = body_start
    while len(body) < content_length:
        remaining = content_length - len(body)
        chunk = conn.recv(4096 if remaining > 4096 else remaining)
        if not chunk:
            break
        body += chunk
    return body[:content_length]

def send_response(conn, status_line, content_type, body):
    hdr = (
        f"HTTP/1.1 {status_line}\r\n"
        f"Content-Type: {content_type}\r\n"
        f"Content-Length: {len(body)}\r\n"
        f"Connection: close\r\n"
        f"\r\n"
    ).encode("utf-8")
    conn.sendall(hdr + body)

def main():
    srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    srv.bind((HOST, PORT))
    srv.listen(50)
    print(f"HTTP rotas em {HOST}:{PORT}")

    while True:
        conn, _ = srv.accept()
        try:
            head, body_start = recv_headers(conn)
            method, path, headers = parse_request(head)

            if method == "GET" and path == "/status":
                payload = {
                    "status": "ok",
                    "timestamp": datetime.datetime.now(datetime.timezone.utc).isoformat(),
                }
                body = json.dumps(payload, ensure_ascii=False).encode("utf-8")
                send_response(conn, "200 OK", "application/json; charset=utf-8", body)
                continue

            if method == "POST" and path == "/echo":
                length = int(headers.get("content-length", "0") or "0")
                if length > MAX_BODY:
                    send_response(conn, "413 Payload Too Large", "text/plain; charset=utf-8", b"Payload Too Large")
                    continue
                body = read_body(conn, body_start, length)
                send_response(conn, "200 OK", "text/plain; charset=utf-8", body)
                continue

            send_response(conn, "404 Not Found", "text/plain; charset=utf-8", b"Not Found")
        finally:
            conn.close()

if __name__ == "__main__":
    main()

Como testar

python3 ex11_http_routes.py
curl -i http://127.0.0.1:8080/status
curl -i -X POST --data 'abc' http://127.0.0.1:8080/echo
curl -i http://127.0.0.1:8080/naoexiste

Como explicar o parsing (texto pronto)

  • A linha de requisição (METHOD PATH HTTP/...) é lida no cabeçalho.
  • Os headers são consumidos até o delimitador \r\n\r\n.
  • O corpo não pode ser lido "até acabar"; ele deve ser lido em loop conforme Content-Length.
  • Sem essa disciplina, o servidor pode truncar payload ou travar esperando bytes que não virão.

Teste extra obrigatório do limite (10 KiB)

# gerar payload acima de 10 KiB e validar 413
python3 - <<'PY' > payload_11k.txt
print("a" * (11 * 1024))
PY

curl -i -X POST --data-binary @payload_11k.txt http://127.0.0.1:8080/echo
Evidência mínima do Ex.11: /status (200 JSON), /echo (200 com corpo), rota inválida (404) e payload acima do limite (413).
EX.12
Reimplementação com http.server e comparação objetiva
Entregar o mesmo comportamento do exercício anterior com menos parsing manual e comparar trade-offs.
# ex12_httpserver.py
from http.server import BaseHTTPRequestHandler, HTTPServer
import datetime
import json

HOST = "0.0.0.0"
PORT = 8080
MAX_BODY = 10 * 1024

class Handler(BaseHTTPRequestHandler):
    def _send(self, status, ctype, body):
        self.send_response(status)
        self.send_header("Content-Type", ctype)
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)

    def do_GET(self):
        if self.path == "/status":
            payload = {
                "status": "ok",
                "timestamp": datetime.datetime.now(datetime.timezone.utc).isoformat(),
            }
            body = json.dumps(payload, ensure_ascii=False).encode("utf-8")
            self._send(200, "application/json; charset=utf-8", body)
            return
        self._send(404, "text/plain; charset=utf-8", b"Not Found")

    def do_POST(self):
        if self.path != "/echo":
            self._send(404, "text/plain; charset=utf-8", b"Not Found")
            return
        length = int(self.headers.get("Content-Length", "0") or "0")
        if length > MAX_BODY:
            self._send(413, "text/plain; charset=utf-8", b"Payload Too Large")
            return
        body = self.rfile.read(length)
        self._send(200, "text/plain; charset=utf-8", body)

    def log_message(self, fmt, *args):
        return

def main():
    server = HTTPServer((HOST, PORT), Handler)
    print(f"http.server em {HOST}:{PORT}")
    server.serve_forever()

if __name__ == "__main__":
    main()

Como testar

python3 ex12_httpserver.py
curl -i http://127.0.0.1:8080/status
curl -i -X POST --data 'abc' http://127.0.0.1:8080/echo
curl -i http://127.0.0.1:8080/naoexiste

Comparação técnica (o que muda em qualidade e risco)

  • Parsing/edge cases: no manual, você controla tudo, mas também assume o risco de errar delimitação de headers/corpo e casos limítrofes.
  • Segurança/hardening: no manual, qualquer proteção extra depende de você; no http.server já há parsing consolidado, mas ainda não é um stack completo de produção.
  • Performance: ambos atendem laboratório; para alto volume, o ponto principal é arquitetura/servidor real (workers, timeout, limits, observabilidade), não só a API usada.
  • Extensibilidade: manual facilita estudar protocolo; http.server acelera evolução de rotas sem reimplementar toda base HTTP.
  • Decisão prática no TP: Ex.11 prova domínio de protocolo; Ex.12 prova maturidade de engenharia ao escolher abstração mais segura para manter.
  • Nota importante: http.server serve muito bem para estudo e ferramentas internas, mas não deve ser tratado como servidor de produção por padrão.
EX.13
OpenSSL: chave privada e certificado autoassinado
Gerar key.pem e cert.pem para ambiente local com CN=localhost e validade de 30 dias.
openssl req -x509 -newkey rsa:2048 -nodes \
  -keyout key.pem \
  -out cert.pem \
  -days 30 \
  -subj "/CN=localhost"

ls -lh key.pem cert.pem

Explicação curta obrigatória (texto pronto)

  • key.pem é a chave privada do servidor: ela assina o handshake e nunca deve ser exposta.
  • cert.pem é o certificado público apresentado ao cliente, contendo identidade e chave pública.
  • Em certificado autoassinado, o emissor não é uma CA pública confiável para navegador/curl por padrão.
  • Por isso, no laboratório, usa-se -k no curl para ignorar validação de cadeia de confiança.
  • Para ambientes mais realistas, inclua SAN (Subject Alternative Name), pois clientes modernos validam SAN e não apenas CN.
Evidência mínima: screenshot do comando de geração e screenshot do ls -lh mostrando os dois arquivos.
O que isso prova: você possui material criptográfico local suficiente para subir servidor TLS de laboratório.
EX.14
Servidor TLS em :8443 encapsulando HTTP mínimo
Reusar a lógica do hello HTTP agora sobre túnel TLS para preparar validação com curl -vk.
Só um servidor por porta por vez: antes de subir outra variação TLS em :8443, encerre o processo anterior para evitar conflito de bind.
# ex14_tls_http_hello.py
import socket
import ssl

HOST = "0.0.0.0"
PORT = 8443

def recv_until(conn, marker=b"\r\n\r\n", limit=8192):
    data = b""
    while marker not in data:
        chunk = conn.recv(1024)
        if not chunk:
            break
        data += chunk
        if len(data) > limit:
            break
    return data

def main():
    context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
    context.load_cert_chain(certfile="cert.pem", keyfile="key.pem")

    srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    srv.bind((HOST, PORT))
    srv.listen(50)
    print(f"TLS HTTP em {HOST}:{PORT}")

    body = b"hello world!"
    headers = (
        b"HTTP/1.1 200 OK\r\n"
        b"Content-Type: text/plain; charset=utf-8\r\n"
        b"Content-Length: 12\r\n"
        b"Connection: close\r\n"
        b"\r\n"
    )
    resp = headers + body

    while True:
        conn, _ = srv.accept()
        try:
            tls_conn = context.wrap_socket(conn, server_side=True)
            _ = recv_until(tls_conn)
            tls_conn.sendall(resp)
            tls_conn.close()
        except ssl.SSLError as e:
            print("TLS erro:", e)
            conn.close()

if __name__ == "__main__":
    main()

Como testar (prévia)

python3 ex14_tls_http_hello.py
ss -tlnp | grep ':8443\b' || true
Nota de permissão: se o PID não aparecer no ss -tlnp, rode com sudo para provar também o dono do socket TLS.
LISTEN em 8443 é evidência parcial. A validação forte do exercício vem no Ex.15 com handshake e resposta HTTP via curl -vk.
EX.15
Teste TLS/HTTPS com curl -vk --resolve
Provar handshake TLS e entrega de resposta HTTP sobre canal criptografado local.
curl -vk --resolve localhost:8443:127.0.0.1 https://localhost:8443/

O que capturar na evidência

  • Trecho do handshake mostrando versão/cifra negociada.
  • Status HTTP 200.
  • Body hello world!.
Isso prova que a sessão TLS foi estabelecida e que a aplicação HTTP respondeu dentro do túnel criptografado.
EX.16
Forçar TLS 1.2+ e conjunto de cifras mais forte
Aplicar endurecimento mínimo do contexto TLS e validar versão negociada com cliente.
Alteração obrigatória no servidor TLS:
context.minimum_version = ssl.TLSVersion.TLSv1_2
context.set_ciphers("HIGH:!aNULL:!eNULL:!MD5:!RC4:!3DES:!DES:!EXP:!PSK:!SRP:!DSS")

Prova mínima

curl -vk --resolve localhost:8443:127.0.0.1 https://localhost:8443/

Prova forte (opcional)

openssl s_client -connect 127.0.0.1:8443 -servername localhost -tls1_2
Evidência recomendada: trecho mostrando protocolo TLSv1.2 ou TLSv1.3 na conexão estabelecida.
CHECK
Checklist final de entrega do TP3
Conferência final para garantir que cada item tenha implementação, teste, evidência e conclusão técnica.

Checklist final de entrega (produto auditável)

  • Cada exercício contém código executável em arquivo próprio.
  • Comandos de teste estão explícitos e reproduzíveis.
  • Evidência é curta e mostra o sinal certo (LISTEN, status, handshake, saída de cliente), sem poluição de terminal.
  • Cada item fecha com frase clara do que foi provado.
  • Exercícios 14-16 mostram TLS funcionando e versão/cifra compatível com o requisito.
  • Nome do PDF segue exatamente a convenção exigida no enunciado (mesma grafia, separadores e extensão).
  • Há pelo menos uma evidência por requisito técnico (ex.: LISTEN, teste funcional com cliente, validação HTTP/TLS).

Padrão de recorte de evidência para o PDF

  • Capturar só o trecho que comprova o requisito (comando + saída essencial).
  • Evitar screenshots muito longos com informação irrelevante.
  • Quando houver mais de um requisito no exercício, anexar evidências separadas e legíveis.

Erros comuns por tema (sinal, causa provável e correção)

Sockets/TCP/UDP

  • Sinal: Address already in use | Causa provável: porta ocupada | Como provar/corrigir: ss -tlnp/ss -ulnp e trocar porta ou finalizar processo.
  • Sinal: cliente não conecta | Causa provável: servidor não está em LISTEN | Como provar/corrigir: validar LISTEN antes do teste cliente.
  • Sinal: eco truncado | Causa provável: leitura/escrita parcial sem loop | Como provar/corrigir: revisar recv/sendall e repetir teste com payload conhecido.

HTTP Manual

  • Sinal: body incompleto em POST | Causa provável: leitura incorreta do Content-Length | Como provar/corrigir: ler em loop até o total de bytes.
  • Sinal: resposta quebrada no cliente | Causa provável: headers mal formados (CRLF) | Como provar/corrigir: confirmar \r\n\r\n entre headers e corpo.
  • Sinal: não retorna 413 | Causa provável: limite não aplicado | Como provar/corrigir: reenviar payload > 10 KiB e validar status 413.

TLS

  • Sinal: erro de certificado no cliente | Causa provável: autoassinado sem CA confiável | Como provar/corrigir: usar curl -vk no laboratório e explicar o motivo.
  • Sinal: handshake falha | Causa provável: arquivo cert.pem/key.pem inválido ou incompatível | Como provar/corrigir: regenerar com OpenSSL e testar de novo.
  • Sinal: versão TLS abaixo do exigido | Causa provável: ausência de minimum_version | Como provar/corrigir: fixar TLS1.2+ e validar no curl -vk/openssl s_client.
Com esses ajustes, a página cobre conteúdo, explicação e critérios de entrega no mesmo nível de um arquivo de respostas didático completo.