NAME

HTTPS::Handy - A tiny HTTPS/1.0 server with TLS written in pure Perl

VERSION

Version 1.01

SYNOPSIS

use HTTPS::Handy;

my $app = sub {
    my ($env) = @_;
    return [200,
        ['Content-Type', 'text/html; charset=utf-8'],
        ['<h1>Hello, HTTPS World!</h1>']];
};

# Starts on https://0.0.0.0:8443/ with a self-signed certificate
# that the module generates by itself, in Perl, in about a second.
HTTPS::Handy->run(app => $app);

# With your own certificate
HTTPS::Handy->run(
    app           => $app,
    port          => 8443,
    ssl_cert_file => 'server-cert.pem',
    ssl_key_file  => 'server-key.pem',
);

TABLE OF CONTENTS

DESCRIPTION

The shortest way in

perl lib/HTTPS/Handy.pm

That starts a demonstration server. The first run spends about a second making a key and a certificate, then prints the address to open. The browser will warn that the certificate vouches for itself; say to continue, and the page appears. Nothing had to be installed, and no other program ran.

What it is

HTTPS::Handy is a small HTTPS/1.0 server for teaching, for reading, and for local development. It speaks a subset of PSGI, so an application is a single code reference that takes $env and returns a three element array reference.

What makes this version unusual is that the TLS layer is not borrowed from anywhere. The whole of it -- big integer arithmetic, the P-256 elliptic curve, SHA-256, HMAC, the TLS pseudo random function, ChaCha20-Poly1305, DER and PEM encoding, X.509 certificate generation, the record layer and the handshake -- is written in Perl inside this one file. The distribution contains no binary file, no XS code, and no compiled component of any kind; the module loads nothing outside the Perl core and runs no external program.

Two things follow from that, and they are the reason the module exists.

The first is that an ordinary browser opens the pages it serves. The cipher suites here are the ones Chrome, Firefox, Safari and Edge still accept: ECDHE for key agreement, ChaCha20-Poly1305 for the data, and an ECDSA certificate. A lesson can therefore start with a real https:// URL in a real browser rather than with a command line tool.

The second is that every step from the first byte of a ClientHello to the HTML the browser displays can be read, printed out, and traced with a print statement. Nothing is hidden behind a library.

If no certificate is supplied, the module generates an elliptic curve key and a self-signed certificate itself, in about a second, and caches them on disk. There is nothing to install and nothing to prepare.

REQUIREMENTS

Perl     : 5.005_03 or later
Modules  : Core only (IO::Socket, POSIX, Carp)
Platform : Windows, UNIX/Linux, and anywhere else Perl runs
External : Nothing. No OpenSSL, no certbot, no compiler.
Clients  : Current browsers, curl, and openssl s_client

DIFFERENCES FROM HTTP::HANDY

  • Transport is HTTPS instead of HTTP.

  • psgi.url_scheme is "https" instead of "http".

  • psgi.ssl is set to 1 in the PSGI environment.

  • Default port is 8443 instead of 8080.

  • A certificate and a private key are needed, and are generated automatically when they are not supplied.

The $env keys, the response format, and every utility method are the same in both modules, so an application written for one runs unchanged on the other.

SUPPORTED PROTOCOL

  • HTTPS/1.0 only (no Keep-Alive)

  • Methods: GET and POST only

  • Connection is closed immediately after each response

  • TLS 1.2 only

  • Key agreement: ECDHE on the P-256 curve, which gives forward secrecy

  • Cipher suites: TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 (0xCCA9) with a generated certificate, and TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 (0xCCA8) when the supplied certificate holds an RSA key

  • Certificates are signed with SHA-256

  • Session resumption by session id, so that only the first connection of a session pays for the public key arithmetic

  • One certificate per server. The name a client asks for through the Server Name Indication extension is not looked at.

These are the suites current browsers still accept, which is the point: https://localhost:8443/ opens in Chrome, Firefox, Safari and Edge, as well as in curl and openssl s_client, with no special options on either side. A self-signed certificate still produces the usual warning page, where the visitor chooses to continue.

HOW TLS IS IMPLEMENTED

Where to start reading

The file holds eight packages, laid out in the order they are meant to be read: each one uses the one below it, so reading downwards is reading from the protocol towards the arithmetic.

Package                 Lines   What it does
----------------------- ------- --------------------------------------
HTTPS::Handy            923     the web server, and the demo at the end
HTTPS::Handy::Input     109     an in memory handle for psgi.input
HTTPS::Handy::TLS       896     the record layer and the handshake
HTTPS::Handy::EC        332     the P-256 curve: ECDH and ECDSA
HTTPS::Handy::ChaCha    211     ChaCha20-Poly1305, the record cipher
HTTPS::Handy::Crypt     187     SHA-256, HMAC, the PRF, randomness
HTTPS::Handy::X509      447     DER, PEM, self-signed certificates
HTTPS::Handy::RSA       77      signing with a certificate from elsewhere
HTTPS::Handy::BigInt    465     multiple precision integers

Comments and blank lines are counted too; about a third of each figure is prose. A test in the distribution compares this table with the file, so it cannot quietly go out of date.

The core is one subroutine: HTTPS::Handy::TLS::server_handshake. It is about a hundred and fifty lines and it contains the whole protocol. Everything else in the file is a part it calls. A reader in a hurry should read that one subroutine, then follow whichever part looks interesting.

What a handshake does

client -> ClientHello         which versions and ciphers it knows
server -> ServerHello         the version and cipher it picked
server -> Certificate         the server public key, signed
server -> ServerKeyExchange   a one time curve point, signed
server -> ServerHelloDone
client -> ClientKeyExchange   its own one time curve point
client -> ChangeCipherSpec    everything after this is encrypted
client -> Finished            a hash of the whole handshake
server -> ChangeCipherSpec
server -> Finished

Each side multiplies its own secret number by the other side's point. The two results are equal, neither side ever sends it, and nobody watching can work it out. That shared value becomes the pre-master secret; the PRF expands it into two keys and two nonces; and every record after that is sealed with ChaCha20-Poly1305.

Because each side throws its secret number away when the connection ends, traffic recorded today cannot be decrypted later even if the server key is stolen afterwards. That property is called forward secrecy.

Why these algorithms

Every choice here was made twice: once for what browsers accept, and once for what fits in a file a student can read.

  • ECDHE on P-256 is the only key agreement current browsers offer that this module could implement in a few hundred lines. It also brings forward secrecy, which RSA key transport does not.

  • ChaCha20-Poly1305 is the fastest AEAD to write in pure Perl: ChaCha20 is nothing but 32 bit addition, exclusive or and rotation, and Poly1305 is one multiplication modulo 2**130 - 5 per 16 bytes, which the big integer package already provides. AES-GCM would need an S-box, a key schedule and multiplication in GF(2**128), and would run slower.

  • An ECDSA certificate can be generated in about a second. An RSA key of comparable strength takes minutes of pure Perl, because it has to search for primes.

PSGI SUBSET SPECIFICATION

PSGI is the agreed shape of a Perl web application: a subroutine that takes one hash of request information and returns one array of response information. Writing to that shape means the same application runs under this module, under HTTP::Handy, and under a full server such as Plack, unchanged. Only the part of PSGI described here is implemented.

Application Interface

A HTTPS::Handy application is a plain code reference that receives a request environment hash and returns a three-element response arrayref:

my $app = sub {
    my ($env) = @_;
    return [$status, \@headers, \@body];
};

Request Environment -- $env

The following keys are provided in the environment hashref passed to the app:

Key                 Description
------------------  ----------------------------------------------
REQUEST_METHOD      "GET" or "POST"
PATH_INFO           URL path (e.g. "/index.html")
QUERY_STRING        Query string ("key=val&..."), without leading "?"
SERVER_NAME         Server hostname
SERVER_PORT         Port number (integer)
CONTENT_TYPE        Content-Type header of POST request
CONTENT_LENGTH      Content-Length of POST body (integer)
HTTP_*              Request headers, uppercased, hyphens as underscores
psgi.input          Object with read() for the POST body (see below)
psgi.errors         \*STDERR
psgi.url_scheme     Always "https"
psgi.ssl            Always 1 (indicates TLS is active)
psgix.tls_cipher    Name of the cipher suite this connection uses
psgix.tls_resumed   1 if the handshake was resumed, 0 if it was full

psgi.input Object

The psgi.input value is a HTTPS::Handy::Input object. It provides:

$env->{'psgi.input'}->read($buf, $length)   # read up to $length bytes
$env->{'psgi.input'}->read($buf, $len, $off) # read with offset
$env->{'psgi.input'}->seek($pos, $whence)   # reposition
$env->{'psgi.input'}->tell()                # current position
$env->{'psgi.input'}->getline()             # read one line
$env->{'psgi.input'}->getlines()            # read all lines

This object works on Perl 5.5.3, which does not support open my $fh, '<', \$scalar.

The two psgix. Keys

psgix. is the conventional prefix for a key that a particular PSGI server adds, and these two are here to be looked at rather than acted on. psgix.tls_cipher is the name of the cipher suite in use. psgix.tls_resumed is 0 on a connection that did the full handshake and 1 on one that resumed a remembered session.

The demo page at /info prints the whole environment, so reloading it shows psgix.tls_resumed turning from 0 into 1 as the session cache starts doing its work. That is the fastest way to see, rather than read about, what session resumption is for.

Response Format

The application must return an arrayref of exactly three elements:

[$status_code, \@headers, \@body]
$status_code

An integer HTTP status code (e.g. 200, 404, 500).

\@headers

A flat arrayref of header name/value pairs, alternating:

['Content-Type', 'text/html', 'X-Custom', 'value']
\@body

An arrayref of strings. All elements are joined and sent as the response body.

['<html>', '<body>Hello</body>', '</html>']

Example:

return [200,
    ['Content-Type', 'text/html; charset=utf-8'],
    ['<h1>Hello HTTPS::Handy</h1>']];

SERVER STARTUP

run(%args)

Starts the HTTPS server. This call blocks indefinitely (until the process is killed). In order, it

  1. finds a certificate and a private key, generating them if it must;

  2. listens on a plain TCP socket;

  3. for each connection in turn: runs the TLS handshake, reads one HTTP request, calls the application, writes the response, and closes the connection.

One connection is handled at a time. The next client waits.

HTTPS::Handy->run(
    app           => $app,     # required: PSGI app code reference
    host          => '127.0.0.1', # optional: bind address (default: 0.0.0.0)
    port          => 8443,     # optional: port number  (default: 8443)
    log           => 1,        # optional: messages to STDERR (default: 1)
    max_post_size => 10485760, # optional: max POST bytes (default: 10MB)

    # TLS certificate options
    ssl_cert_file => 'server-cert.pem',    # optional
    ssl_key_file  => 'server-key.pem',     # optional
    domains       => 'example.com',        # optional
    cert_dir      => '.https_handy_certs', # optional
);
log

When true, which it is by default, this module writes to STDERR: the two lines it prints at startup, one access log line per request, and a line for each certificate step, failed handshake or application error. When false it writes nothing at all, and an application that still wants to record something can write to psgi.errors itself.

ssl_cert_file / ssl_key_file

Paths to an existing TLS certificate and private key, in PEM form. When both are given, no certificate is generated. The key may be an elliptic curve key on the P-256 curve (BEGIN EC PRIVATE KEY) or an RSA key (BEGIN RSA PRIVATE KEY), and either may arrive in the PKCS #8 wrapper that says BEGIN PRIVATE KEY. An encrypted key is not supported. The certificate file may hold a chain, and every certificate in it is sent.

domains

Host name, or an arrayref of host names, that this server answers for. If a Let's Encrypt certificate for the first name is already installed below /etc/letsencrypt/live/, it is used. Otherwise the names go into the self-signed certificate that is generated instead.

cert_dir

Directory for the generated certificate and key. Default: .https_handy_certs

Access Log Format (LTSV)

When log is enabled, each request is written to STDERR as a single LTSV (Labeled Tab-separated Values) line:

time:2026-01-01T12:00:00\tmethod:GET\tpath:/index.html\tstatus:200\tsize:1234\tua:Mozilla/5.0\treferer:

Fields:

time      ISO 8601 local timestamp (YYYY-MM-DDTHH:MM:SS)
method    HTTP method (GET or POST)
path      Request path (PATH_INFO, without query string)
status    HTTP status code
size      Response body size in bytes
ua        User-Agent header value (empty string if absent)
referer   Referer header value (empty string if absent)

LTSV can be parsed line by line with split /\t/ and each field with split /:/, $field, 2. It is directly compatible with LTSV::LINQ.

CERTIFICATES

How the certificate is chosen

  1. ssl_cert_file and ssl_key_file, when both are given.

  2. An existing Let's Encrypt certificate for the first name in domains, when one is installed on this machine.

  3. A self-signed certificate generated by this module and cached in cert_dir as selfsigned-cert.pem and selfsigned-key.pem. Later runs find those files and start at once.

The generated certificate

The certificate is a version 3 X.509 certificate holding a P-256 public key, signed with ECDSA and SHA-256 by the matching private key. It is valid for 397 days, because browsers refuse a certificate whose lifetime is much longer than that. It carries the host name as the common name and as a subjectAltName, together with localhost and 127.0.0.1, so that a browser matches it for local use.

Browsers still warn about it, because nothing vouches for it but itself. In Chrome the warning is ERR_CERT_AUTHORITY_INVALID and the way past it is "Advanced", then "Proceed"; Firefox and Safari have the same door under different labels. That warning is the only obstacle between a class and a working https:// page.

Using a certificate from elsewhere

Certificates made by other tools work, as long as the key is a P-256 elliptic curve key or an RSA key, and the file is not encrypted. For example:

openssl ecparam -genkey -name prime256v1 -out server-key.pem
openssl req -x509 -new -key server-key.pem -days 397 \
    -subj /CN=localhost -out server-cert.pem

The same files are then passed to ssl_cert_file and ssl_key_file. Obtaining a certificate from a certificate authority over ACME is not implemented here; it needs a client of its own.

METHODS

Everything below is called as a class method, with an arrow and the module name in front:

HTTPS::Handy->response_text('hello');

They are helpers for writing the application, and none of them touch the network. run(), which does, is described under "SERVER STARTUP" above.

serve_static($env, $docroot [, %opts])

Serve a static file from $docroot using PATH_INFO as the file path. Returns a complete PSGI response arrayref.

my $res = HTTPS::Handy->serve_static($env, './htdocs');

# With cache control (e.g. for htmx apps: cache JS/CSS, never cache HTML)
my $res = HTTPS::Handy->serve_static($env, './htdocs', cache_max_age => 3600);

Options:

cache_max_age

Sets the Cache-Control header.

cache_max_age => 3600   # Cache-Control: public, max-age=3600
cache_max_age => 0      # Cache-Control: no-cache
(not specified)         # Cache-Control: no-cache  (default)

For htmx applications, setting a positive cache_max_age for static assets (CSS, JS, images) while leaving HTML fragments at the default no-cache prevents stale scripts from being reused after a partial page update.

Behaviour:

  • MIME type is detected automatically from the file extension

  • Supported types: html, htm, txt, css, js, json, xml, png, jpg, jpeg, gif, ico, svg, pdf, zip, gz, ltsv, csv, tsv

  • Directory access attempts to serve index.html

  • Returns 404 if the file does not exist

  • Returns 403 if the file cannot be opened

  • Path traversal (..) is blocked with a 403 response

url_decode($str)

Decode a percent-encoded URL string. + is decoded as a space.

my $str = HTTPS::Handy->url_decode('hello+world%21');
# returns: "hello world!"

parse_query($query_string)

Parse a URL query string into a hash. When the same key appears more than once, its value becomes an arrayref.

my %p = HTTPS::Handy->parse_query('name=ina&tag=perl&tag=cpan');
# $p{name} eq 'ina'
# $p{tag}  is ['perl', 'cpan']

mime_type($ext)

Return the MIME type string for a given file extension. The leading dot is optional.

HTTPS::Handy->mime_type('html');   # 'text/html; charset=utf-8'
HTTPS::Handy->mime_type('.json');  # 'application/json'
HTTPS::Handy->mime_type('xyz');    # 'application/octet-stream'

is_htmx($env)

Returns 1 if the request was made by htmx (i.e. the HX-Request: true header is present), or 0 otherwise.

if (HTTPS::Handy->is_htmx($env)) { ... }

response_redirect($location, [$code])

Build a redirect response. Default status is 302.

HTTPS::Handy->response_redirect('/new/path');
HTTPS::Handy->response_redirect('/new/path', 301);

response_json($json_str, [$code])

Build a JSON response. The caller must provide already-encoded JSON.

HTTPS::Handy->response_json('{"ok":true}');
HTTPS::Handy->response_json('{"error":"bad"}', 400);

response_html($html, [$code])

Build an HTML response.

HTTPS::Handy->response_html('<h1>Hello</h1>');

response_text($text, [$code])

Build a plain text response.

HTTPS::Handy->response_text('Hello, World!');

SECURITY

Do not use this module to protect anything of value. It is written to be read, and a cryptographic implementation that is easy to read is not the same thing as one that is safe to rely on. Use it on a private network, on a development machine, or in a classroom, and put a mature server such as nginx or Apache in front of anything that matters.

The rest of this section says exactly what is weak about it, because a list of specific weaknesses teaches more than a general warning does.

  • The implementation is not hardened. Comparisons, the authentication tag check and the curve arithmetic are written for clarity, so they take different amounts of time for different inputs. An attacker who can measure those times on the same network can learn things a constant time implementation would hide.

  • Randomness depends on the platform. Keys, nonces and the per signature value k come from /dev/urandom when it can be read. Where it cannot -- on Windows, for instance -- the module falls back to hashing the process id, the clock and rand, which is far weaker. For ECDSA that is worse than it sounds: two signatures made with the same k give away the private key. Supply your own certificate on such systems if the key matters.

  • Self-signed certificates make browsers warn. That warning is correct: nothing vouches for the certificate. It is fine for local work and wrong for the public internet.

  • Certificates are not verified by the built-in client. The client side exists for the test suite, and it accepts whatever certificate it is shown. It is not a general purpose TLS client.

  • One process, one connection at a time. A slow or hostile client blocks every other client while it is connected.

  • The session cache is in memory and not shared. It holds at most sixty-four sessions for an hour each, and it disappears when the process does.

On the other hand, the protocol itself is the current one: TLS 1.2 with forward secrecy and an authenticated cipher. A record that is altered in transit is refused rather than decrypted, and traffic recorded today stays unreadable even if the server key is taken tomorrow.

PERFORMANCE

All cryptography here runs in Perl. On a current machine:

  • Generating the key and the self-signed certificate takes about a second, once, and the result is cached in cert_dir.

  • A full handshake costs three elliptic curve multiplications, which is between one and two seconds.

  • A resumed handshake costs none of them and is effectively instant. Since a browser opens several connections and makes several requests per page, this is what makes a demonstration usable: the first connection is slow and the rest are not.

  • Bulk data goes through ChaCha20-Poly1305 at roughly two hundred kilobytes per second. Pages of a few kilobytes are comfortable; large downloads are not.

  • Because HTTP/1.0 closes the connection after every response, every request needs a new handshake, though a resumed one. Keeping demonstration pages self-contained, with no external images or scripts, makes them appear at once.

LIMITATIONS

  • Single-process, single-thread. Requests are handled one at a time. Not suitable for production or for high load.

  • One curve and one cipher. P-256 and ChaCha20-Poly1305 only. A client that offers neither cannot connect; every current browser offers both.

  • TLS 1.2 only. TLS 1.3 is a different handshake and is not implemented. Browsers fall back to 1.2 without complaint.

  • No ALPN, no renegotiation, no client certificates, no OCSP, no session tickets (session ids only).

  • No Server Name Indication. One certificate is served to every client, whatever host name was asked for.

  • The built-in client does not resume sessions and does not check certificates. It exists for the test suite.

  • No ACME client. Certificates from a certificate authority must be obtained by other means and passed in.

  • POST body fully buffered. The entire POST body is read into memory before the application runs.

  • No Keep-Alive. Every request closes the connection.

  • No cookie or session management (implement in the application layer).

  • Headers a client sends more than once keep only the last value.

None of this affects the application: it sees an ordinary PSGI environment and returns an ordinary PSGI response, with the encryption and the certificate entirely behind it.

DEMO

Run directly to start a self-contained demo server:

perl lib/HTTPS/Handy.pm           # from the distribution directory
perl lib/HTTPS/Handy.pm 9443      # on port 9443

The first run generates a key and a self-signed certificate and stores them in .https_handy_certs, which takes about a second; later runs start at once. Then open https://localhost:8443/ (or the port you specified) in a browser, and click past the certificate warning. The demo provides three built-in pages:

/

Top page with a GET query form and a POST form.

/echo

Echoes GET query parameters or POST form fields in a table. Demonstrates parse_query for both methods.

/info

Displays the full PSGI $env hash for the current request, including psgi.url_scheme and psgi.ssl.

DIAGNOSTICS

Startup errors

HTTPS::Handy->run: 'app' is required

run() was called without an app argument.

HTTPS::Handy->run: 'app' must be a code reference

The app argument was not a code reference.

HTTPS::Handy->run: 'port' must be a number

The port argument contained something other than digits.

HTTPS::Handy->run: 'max_post_size' must be a number

The max_post_size argument contained something other than digits.

HTTPS::Handy: Cannot bind to <host>:<port> - <reason>

The listening socket could not be created. The port may be in use, or below 1024 without the privilege to bind it.

HTTPS::Handy: Cannot read certificate file <file>: <reason>

ssl_cert_file could not be opened.

HTTPS::Handy: No CERTIFICATE block found in <file>

The certificate file contains no -----BEGIN CERTIFICATE----- block. A DER file must be converted to PEM first.

HTTPS::Handy: Cannot read private key file <file>: <reason>

ssl_key_file could not be opened.

HTTPS::Handy: No PRIVATE KEY block found in <file>

The key file contains none of BEGIN EC PRIVATE KEY, BEGIN RSA PRIVATE KEY or BEGIN PRIVATE KEY.

HTTPS::Handy: <file> is not a P-256 or RSA private key this module can read

The key is encrypted, or it is on a curve other than P-256. Decrypt it, or make a key this module understands, with

openssl ec -in old-key.pem -out server-key.pem
HTTPS::Handy: Cannot write <file>: <reason>

The generated certificate or key could not be saved. Check that cert_dir exists and is writable.

Internal errors

These come from the cryptographic packages and indicate a bug or a damaged key file rather than a configuration mistake.

HTTPS::Handy::BigInt: division by zero

A modulus of zero reached the arithmetic, which a valid key never does.

Runtime messages (STDERR)

These are written only when the log option is true, which is the default.

[TIMESTAMP] App error: MESSAGE

The application died. A 500 response was sent.

[TIMESTAMP] Accept failed: MESSAGE

accept failed. The server continues.

[TIMESTAMP] TLS handshake failed: MESSAGE

A client connected but the handshake did not finish. The connection is closed and the server continues. MESSAGE is one of:

not a TLS record (is the client speaking plain HTTP?)

The first bytes were not a TLS record. Almost always an http:// URL aimed at the HTTPS port.

client offered an older protocol than TLS 1.2

The client is older than this module supports.

no cipher suite in common (this server speaks NAME over the P-256 curve)

The client offered neither of the two suites here, or does not support the P-256 curve.

the ClientHello was too short to read

The first handshake message was truncated or malformed.

the client sent a curve point this server cannot read
the client sent a point that is not on the P-256 curve

The ClientKeyExchange did not hold a valid P-256 point. A correct client does not do this.

client Finished did not verify

The two sides did not arrive at the same keys, or the handshake was tampered with in transit.

record failed its authentication tag

An encrypted record was altered, reordered or replayed.

alert from peer: LEVEL/DESCRIPTION

The client gave up and said why, in the numbers of RFC 5246 section 7.2. The commonest is 2/48, which means the client would not accept the certificate.

[TIMESTAMP] Generating a P-256 key and a self-signed certificate for HOST ...

No cached certificate was found in cert_dir, so one is being made. It takes about a second, and the next line appears when it is done.

BUGS AND LIMITATIONS

Please report any bugs or feature requests by e-mail to <ina.cpan@gmail.com>.

When reporting a bug, please include:

  • A minimal, self-contained test script that reproduces the problem.

  • The version of HTTPS::Handy:

    perl -MHTTPS::Handy -e 'print HTTPS::Handy->VERSION, "\n"'
  • Your Perl version:

    perl -V
  • Your operating system, and, for a handshake problem, the client you used and the output of

    openssl s_client -connect localhost:8443 -tls1_2

See "LIMITATIONS" above for known design limitations.

SEE ALSO

HTTP::Handy -- the plain-HTTP sibling of this module, by the same author. HTTPS::Handy is HTTP::Handy plus TLS; the $env hash, response format, and all utility methods are identical.

IO::Socket::SSL -- the usual way to add TLS to a Perl socket, backed by OpenSSL. Faster, safer and far larger than the TLS layer here; the right choice whenever the goal is to protect something rather than to study the protocol.

Plack -- a full-featured PSGI toolkit and server collection. Requires Perl 5.8+. For production use or more demanding workloads, migrating from HTTPS::Handy to Plack (with a reverse proxy such as nginx terminating TLS) is straightforward.

Standards implemented here

RFC 5246 (TLS 1.2), RFC 8422 (elliptic curves in TLS), RFC 7905 (ChaCha20-Poly1305 in TLS), RFC 8439 (ChaCha20 and Poly1305), RFC 5746 (renegotiation indication, the empty extension only), RFC 8017 (PKCS #1 v1.5), RFC 5280 (X.509), SEC 1 (elliptic curve keys), RFC 2104 (HMAC), RFC 4648 (Base64), FIPS 180-4 (SHA-256), FIPS 186-4 (the P-256 curve and ECDSA).

AUTHOR

INABA Hitoshi <ina.cpan@gmail.com>

COPYRIGHT AND LICENSE

This software is free software; you can redistribute it and/or modify it under the same terms as Perl itself.