package transport import ( "crypto/sha256" "crypto/x509" "encoding/hex" "log/slog" ) // LogHandshakeOK emits a structured slog record for a successful mTLS // handshake. Per REQ-038, the fields are: event=mtls.handshake, // result=ok, peer, cert_fp. func LogHandshakeOK(log *slog.Logger, peer, certFP string) { if log == nil { return } log.Info("mtls.handshake", slog.String("event", "mtls.handshake"), slog.String("result", "ok"), slog.String("peer", peer), slog.String("cert_fp", certFP), ) } // LogHandshakeFailed emits a structured slog record for a failed mTLS // handshake. Per REQ-038, the fields are: event=mtls.handshake, // result=failed, peer, cert_fp (may be empty if no cert was presented // before the failure), err. The log level is WARN — handshake failures // are operationally interesting but not always fatal (e.g., a scanner // probing the port). func LogHandshakeFailed(log *slog.Logger, peer, certFP string, err error) { if log == nil { return } attrs := []any{ slog.String("event", "mtls.handshake"), slog.String("result", "failed"), slog.String("peer", peer), slog.String("cert_fp", certFP), } if err != nil { attrs = append(attrs, slog.String("err", err.Error())) } log.Warn("mtls.handshake", attrs...) } // LogHandshakeFromCert is a convenience wrapper that pulls the fingerprint // off a parsed *x509.Certificate and calls LogHandshakeOK. func LogHandshakeFromCert(log *slog.Logger, peer string, cert *x509.Certificate) { if cert == nil { LogHandshakeOK(log, peer, "") return } LogHandshakeOK(log, peer, FingerprintOfCert(cert)) } // FingerprintOfCert is a thin wrapper that returns the SHA-256 hex of a // cert's DER bytes. Re-exported here so transport callers don't need // to import the security package directly. func FingerprintOfCert(cert *x509.Certificate) string { if cert == nil { return "" } sum := sha256.Sum256(cert.Raw) return hex.EncodeToString(sum[:]) }