tls: surface transport TLS status in email path and authentication
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
Parse TLS details (version, cipher, bits, cert verification) from the Postfix Received header parenthetical and expose them per hop, rendered as a per-hop badge in the Email Path card. Add an x-tls Authentication-Results result: parse it when present, and otherwise synthesize it from the inbound hop's TLS info. A negative result (unencrypted inbound connection) applies a -10 authentication score penalty and is shown in the Authentication card. Enable the TLS handler in authentication_milter. Closes: #40
This commit is contained in:
parent
8e7e56851b
commit
d53c1b1e00
11 changed files with 593 additions and 0 deletions
|
|
@ -218,6 +218,40 @@
|
|||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- X-TLS (Transport encryption) -->
|
||||
{#if authentication.x_tls}
|
||||
<div class="list-group-item" id="authentication-x-tls">
|
||||
<div class="d-flex align-items-start">
|
||||
<i
|
||||
class="bi {getAuthResultIcon(
|
||||
authentication.x_tls.result,
|
||||
true,
|
||||
)} {getAuthResultClass(authentication.x_tls.result, true)} me-2 fs-5"
|
||||
></i>
|
||||
<div>
|
||||
<strong>Transport TLS</strong>
|
||||
<i
|
||||
class="bi bi-info-circle text-muted ms-1"
|
||||
title="Whether the inbound connection that delivered this message used TLS encryption (x-tls). Falls back to the inbound Received hop when no x-tls header is present."
|
||||
></i>
|
||||
<span
|
||||
class="text-uppercase ms-2 {getAuthResultClass(
|
||||
authentication.x_tls.result,
|
||||
true,
|
||||
)}"
|
||||
>
|
||||
{authentication.x_tls.result}
|
||||
</span>
|
||||
{#if authentication.x_tls.details}
|
||||
<div class="small text-muted mt-1">
|
||||
{authentication.x_tls.details}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- SPF (Required) -->
|
||||
<div class="list-group-item">
|
||||
<div class="d-flex align-items-start" id="authentication-spf">
|
||||
|
|
|
|||
|
|
@ -7,6 +7,21 @@
|
|||
}
|
||||
|
||||
let { receivedChain }: Props = $props();
|
||||
|
||||
// Mirror of the backend protocolIndicatesTLS (RFC 3848): the transport keyword
|
||||
// gains a trailing "S" when TLS was used (ESMTPS, ESMTPSA, SMTPS, LMTPS, LMTPSA...).
|
||||
function protocolIndicatesTLS(withProto: string | undefined | null): boolean {
|
||||
if (!withProto) return false;
|
||||
const p = withProto.trim().toUpperCase();
|
||||
return p.endsWith("S") || p.endsWith("SA");
|
||||
}
|
||||
|
||||
// RFC 3848: a trailing "A" means the sender authenticated (SMTP AUTH):
|
||||
// ESMTPA, ESMTPSA, LMTPA, LMTPSA...
|
||||
function protocolIndicatesAuth(withProto: string | undefined | null): boolean {
|
||||
if (!withProto) return false;
|
||||
return withProto.trim().toUpperCase().endsWith("A");
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if receivedChain && receivedChain.length > 0}
|
||||
|
|
@ -60,6 +75,63 @@
|
|||
{/if}
|
||||
</p>
|
||||
{/if}
|
||||
<p class="mb-0 small d-flex flex-wrap align-items-center gap-3">
|
||||
{#if hop.tls}
|
||||
<span class="badge bg-success">
|
||||
<i class="bi bi-lock-fill me-1"></i>TLS
|
||||
</span>
|
||||
{#if hop.tls.version}
|
||||
<span>
|
||||
<span class="text-muted">Version:</span>
|
||||
<code>{hop.tls.version}</code>
|
||||
</span>
|
||||
{/if}
|
||||
{#if hop.tls.cipher}
|
||||
<span>
|
||||
<span class="text-muted">Cipher:</span>
|
||||
<code>{hop.tls.cipher}</code>
|
||||
</span>
|
||||
{/if}
|
||||
{#if hop.tls.bits}
|
||||
<span>
|
||||
<span class="text-muted">Strength:</span>
|
||||
<code>{hop.tls.bits} bits</code>
|
||||
</span>
|
||||
{/if}
|
||||
{#if hop.tls.verified !== undefined}
|
||||
<span
|
||||
class:text-success={hop.tls.verified}
|
||||
class:text-warning={!hop.tls.verified}
|
||||
>
|
||||
<i
|
||||
class="bi {hop.tls.verified
|
||||
? 'bi-patch-check-fill'
|
||||
: 'bi-patch-exclamation-fill'} me-1"
|
||||
></i>
|
||||
{hop.tls.verified
|
||||
? "Certificate trusted"
|
||||
: "Certificate not trusted"}
|
||||
</span>
|
||||
{/if}
|
||||
{:else if protocolIndicatesTLS(hop.with)}
|
||||
<span class="badge bg-success">
|
||||
<i class="bi bi-lock-fill me-1"></i>TLS
|
||||
</span>
|
||||
{:else if hop.with}
|
||||
<span class="badge bg-secondary">
|
||||
<i class="bi bi-unlock me-1"></i>No TLS
|
||||
</span>
|
||||
{:else}
|
||||
<span class="badge bg-light text-muted border">
|
||||
<i class="bi bi-question-circle me-1"></i>TLS unknown
|
||||
</span>
|
||||
{/if}
|
||||
{#if protocolIndicatesAuth(hop.with)}
|
||||
<span class="badge bg-info">
|
||||
<i class="bi bi-person-check-fill me-1"></i>Authenticated
|
||||
</span>
|
||||
{/if}
|
||||
</p>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue