=encoding UTF-8

=head1 NAME

PAGI::Spec::Tls - The PAGI TLS extension for reporting connection security in scope

=head1 PAGI TLS Extension

B<Version>: 0.3 (Draft)

This specification outlines how to report TLS (or SSL) connection information
in the PAGI I<connection scope> object.

=head2 The Base Protocol

TLS is not usable on its own; it always wraps another protocol.
So this specification is not designed to be usable on its own,
it must be used as an extension to another PAGI specification.
That other PAGI specification is referred to as the I<base protocol
specification>.

For HTTP-over-TLS (HTTPS), use this TLS specification and the
PAGI HTTP specification. The I<base protocol specification> is the
PAGI HTTP specification. (See L<HTTP and WebSocket Protocol|PAGI::Spec::Www>)

For WebSockets-over-TLS (wss:// protocol), use this TLS specification
and the PAGI WebSockets specification. The I<base protocol specification>
is the PAGI WebSockets specification. (See L<HTTP and WebSocket Protocol|PAGI::Spec::Www>)

If using this extension with other protocols (not HTTPS or WebSockets), note
that the I<base protocol specification> must define the I<connection scope> in a
way that ensures it covers at most one TLS connection. If not, you cannot use
this extension.

=head2 When to use this extension

This extension must only be used for TLS connections.

For non-TLS connections, the PAGI server is forbidden from providing this
extension.

A PAGI application can check for the presence of the C<"tls"> extension in
the C<extensions> dictionary in the connection scope. If present, the server
supports this extension and the connection is over TLS. If not present,
either the server does not support this extension or the connection is not
over TLS.

=head2 TLS Connection Scope

The I<connection scope> information passed in C<scope> contains an
C<"extensions"> key, which contains a hashref of extensions. Inside that
hashref, the key C<"tls"> identifies the extension specified in this
document. Servers B<must> only include C<<< extensions->{tls} >>> for TLS connections;
the key must be omitted entirely for cleartext connections. The value is a hashref with the following entries:

=over

=item -

B<server_cert> (String or C<undef>)

The PEM-encoded conversion of the x509 certificate sent by the server when
establishing the TLS connection. Servers that terminate TLS elsewhere may not
have access to this information; in that case set C<server_cert> to C<undef>.

=item -

B<client_cert_chain> (ArrayRef[String])

PEM-encoded certificates supplied by the client. The array is ordered from
leaf to root (client certificate first). If the client did not provide a
certificate, the array must be empty. Servers that cannot retrieve this data
must also return an empty array.

=item -

B<client_cert_name> (String or C<undef>)

The x509 Distinguished Name of the Subject of the client certificate encoded
per L<RFC4514|https://tools.ietf.org/html/rfc4514>. If no certificate was
supplied, or if the server cannot provide the value, set C<undef>. When
C<client_cert_chain> is non-empty this field, if present, must correspond to
the first certificate.

=item -

B<client_cert_error> (String or C<undef>)

C<undef> if a client certificate was provided and successfully verified (or was
not provided). If verification failed but the connection remained open, this
value should contain an informative error string for logging or diagnostics.

=item -

B<tls_version> (Int or C<undef>)

The numeric TLS version in use. Values follow the TLS specification encoding
(C<0x0303> for TLS 1.2, C<0x0304> for TLS 1.3, etc.). Servers that cannot
determine the version must use C<undef>.

=item -

B<cipher_suite> (Int or C<undef>)

The negotiated cipher suite as a 16-bit unsigned integer (e.g., C<0x1301> for
C<TLS_AES_128_GCM_SHA256>). Servers that cannot determine the value must use
C<undef>.

=back

Applications can rely on C<<< scheme => 'https' >>>/C<'wss'> in conjunction with the
presence of C<<< extensions->{tls} >>> when interoperating with PSGI adapters.

=head2 Events

All events are as defined in the I<base protocol specification>.

=head2 Rationale (Informative)

This section explains the choices that led to this specification.

Providing the entire TLS certificates in C<client_cert_chain>, rather than a
parsed subset:

=over

=item -

Makes it easier for web servers to implement, as they do not have to
include a parser for the entirety of the x509 certificate specifications
(which are huge and complicated). They just have to convert the binary
DER format certificate from the wire, to the text PEM format. That is
supported by many off-the-shelf libraries.

=item -

Makes it easier for web servers to maintain, as they do not have to update
their parser when new certificate fields are defined.

=item -

Makes it easier for clients as there are plenty of existing x509 libraries
available that they can use to parse the certificate; they don't need to
do some special PAGI-specific thing.

=item -

Improves interoperability as this is a simple, well-defined encoding, that
clients and servers are unlikely to get wrong.

=item -

Makes it much easier to write this specification. There is no standard
documented format for a parsed certificate in Perl, and we would need to
write one.

=item -

Makes it much easier to maintain this specification. There is no need
to update a parsed certificate specification when new certificate fields
are defined.

=item -

Allows the client to support new certificate fields without requiring
any server changes, so long as the fields are marked as "non-critical" in
the certificate. (A x509 parser is allowed to ignore non-critical fields
it does not understand. Critical fields that are not understood cause
certificate parsing to fail).

=item -

Allows the client to do weird and wonderful things with the raw certificate,
instead of placing arbitrary limits on it.

=back

Specifying C<tls_version> as an integer, not a string or float:

=over

=item -

Avoids maintenance effort in this specification. If a new version of TLS is
defined, then no changes are needed in this specification.

=item -

Does not significantly affect servers. Whatever format we specified, servers
would likely need a lookup table from what their TLS library reports to what
this API needs. (Unless their TLS library provides access to the raw value,
in which case it can be reported via this API directly).

=item -

Does not significantly affect clients. Whatever format we specified, clients
would likely need a lookup table from what this API reports to the values
they support and wish to use internally.

=back

Specifying C<cipher_suite> as an integer, not a string:

=over

=item -

Avoids significant effort to compile a list of cipher suites in this
specification. There are a huge number of existing TLS cipher suites, many
of which are not widely used, and even listing them all would be a huge
effort.

=item -

Avoids maintenance effort in this specification. If a new cipher suite is
defined, then no changes are needed in this specification.

=item -

Avoids dependencies on nonstandard TLS-library-specific names. E.g. the
cipher names used by OpenSSL are different from the cipher names used by the
RFCs.

=item -

Does not significantly affect servers. Whatever format we specified, (unless
it was a nonstandard library-specific name and the server happened to use
that library), servers would likely need a lookup table from what their TLS
library reports to what this API needs. (Unless their TLS library
provides access to the raw value, in which case it can be reported via this
API directly).

=item -

Does not significantly affect clients. Whatever format we specified, clients
would likely need a lookup table from what this API reports to the values
they support and wish to use internally.

=item -

Using a single integer, rather than a pair of integers, makes handling this
value simpler and faster.

=back

C<client_cert_name> duplicates information that is also available in
C<client_cert_chain>. However, many PAGI applications will probably find
that information is sufficient for their application - it provides a simple
string that identifies the user. It is simpler to use than parsing the x509
certificate. For the server, this information is readily available.

There are theoretical interoperability problems with C<client_cert_name>,
since it depends on a list of object ID names that is maintained by IANA and
theoretically can change. In practice, this is not a real problem, since the
object IDs that are actually used in certificates have not changed in many
years. So in practice it will be fine.

=head2 Version History

=over

=item -

0.3 (Draft): No changes from 0.2

=item -

0.2 (Draft): No changes from 0.1

=item -

0.1 (Draft): Initial TLS extension specification

=back

=head2 Example Usage in Modern Perl

Below is a short example of how a B<PAGI> application might check for TLS
extension information in the C<scope> using B<C<Future::AsyncAwait>>.
It illustrates conceptually how you could access the TLS data if present.
Note that real-world usage may involve more checks or error handling.

    use strict;
    use warnings;
    use Future::AsyncAwait;

    async sub app {
        my ($scope, $receive, $send) = @_;
        # Check for TLS extension
        if (exists $scope->{extensions}{tls}) {
            my $tls_info = $scope->{extensions}{tls};

            # For example, check if we have a client certificate name:
            if (defined $tls_info->{client_cert_name}) {
                # Log or process the client's certificate info
                warn "Client cert name: $tls_info->{client_cert_name}\n";
            }

            # Check cipher suite if needed
            if (defined $tls_info->{cipher_suite}) {
                warn sprintf("TLS cipher suite: 0x%04x\n", $tls_info->{cipher_suite});
            }
        }

        # ... proceed with your request handling for the base protocol ...
        # For instance, if this is an HTTP request:
        if ($scope->{type} eq 'http') {
            await $send->({
                type    => 'http.response.start',
                status  => 200,
                headers => [ [ 'content-type', 'text/plain' ] ],
            });
            await $send->({
                type      => 'http.response.body',
                body      => "Hello over TLS!",
                more      => 0,
            });
        }
        else {
            die "Unsupported protocol type: $scope->{type}";
        }
    }

=head2 Copyright

This document has been placed in the public domain.

