|
|
@@ -0,0 +1,371 @@ |
|
|
|
<!doctype html><!-- This is a valid HTML5 document. --> |
|
|
|
<!-- Screen readers, SEO, extensions and so on. --> |
|
|
|
<html lang="fr"> |
|
|
|
<!-- Has to be within the first 1024 bytes, hence before the <title> |
|
|
|
See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset --> |
|
|
|
<meta charset="utf-8"> |
|
|
|
<!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 --> |
|
|
|
<!-- The viewport meta is quite crowded and we are responsible for that. |
|
|
|
See: https://codepen.io/tigt/post/meta-viewport-for-2015 --> |
|
|
|
<meta name="viewport" content="width=device-width,initial-scale=1"> |
|
|
|
<!-- Required to make a valid HTML5 document. --> |
|
|
|
<title>Plaintext HTTP in a Modern World (archive) — David Larlet</title> |
|
|
|
<meta name="description" content="Publication mise en cache pour en conserver une trace."> |
|
|
|
<!-- That good ol' feed, subscribe :). --> |
|
|
|
<link rel="alternate" type="application/atom+xml" title="Feed" href="/david/log/"> |
|
|
|
<!-- Generated from https://realfavicongenerator.net/ such a mess. --> |
|
|
|
<link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons2/apple-touch-icon.png"> |
|
|
|
<link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons2/favicon-32x32.png"> |
|
|
|
<link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons2/favicon-16x16.png"> |
|
|
|
<link rel="manifest" href="/static/david/icons2/site.webmanifest"> |
|
|
|
<link rel="mask-icon" href="/static/david/icons2/safari-pinned-tab.svg" color="#07486c"> |
|
|
|
<link rel="shortcut icon" href="/static/david/icons2/favicon.ico"> |
|
|
|
<meta name="msapplication-TileColor" content="#f0f0ea"> |
|
|
|
<meta name="msapplication-config" content="/static/david/icons2/browserconfig.xml"> |
|
|
|
<meta name="theme-color" content="#f0f0ea"> |
|
|
|
<!-- Documented, feel free to shoot an email. --> |
|
|
|
<link rel="stylesheet" href="/static/david/css/style_2020-06-19.css"> |
|
|
|
<!-- See https://www.zachleat.com/web/comprehensive-webfonts/ for the trade-off. --> |
|
|
|
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin> |
|
|
|
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin> |
|
|
|
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin> |
|
|
|
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin> |
|
|
|
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin> |
|
|
|
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin> |
|
|
|
<script> |
|
|
|
function toggleTheme(themeName) { |
|
|
|
document.documentElement.classList.toggle( |
|
|
|
'forced-dark', |
|
|
|
themeName === 'dark' |
|
|
|
) |
|
|
|
document.documentElement.classList.toggle( |
|
|
|
'forced-light', |
|
|
|
themeName === 'light' |
|
|
|
) |
|
|
|
} |
|
|
|
const selectedTheme = localStorage.getItem('theme') |
|
|
|
if (selectedTheme !== 'undefined') { |
|
|
|
toggleTheme(selectedTheme) |
|
|
|
} |
|
|
|
</script> |
|
|
|
|
|
|
|
<meta name="robots" content="noindex, nofollow"> |
|
|
|
<meta content="origin-when-cross-origin" name="referrer"> |
|
|
|
<!-- Canonical URL for SEO purposes --> |
|
|
|
<link rel="canonical" href="https://jcs.org/2021/01/06/plaintext"> |
|
|
|
|
|
|
|
<body class="remarkdown h1-underline h2-underline h3-underline hr-center ul-star pre-tick"> |
|
|
|
|
|
|
|
<article> |
|
|
|
<header> |
|
|
|
<h1>Plaintext HTTP in a Modern World</h1> |
|
|
|
</header> |
|
|
|
<nav> |
|
|
|
<p class="center"> |
|
|
|
<a href="/david/" title="Aller à l’accueil">🏠</a> • |
|
|
|
<a href="https://jcs.org/2021/01/06/plaintext" title="Lien vers le contenu original">Source originale</a> |
|
|
|
</p> |
|
|
|
</nav> |
|
|
|
<hr> |
|
|
|
<p>On the modern web, everything must be encrypted. |
|
|
|
Unencrypted websites are treated as relics of the past with browsers declaring |
|
|
|
them toxic waste not to be touched (or |
|
|
|
<a href="//blog.mozilla.org/security/2020/11/17/firefox-83-introduces-https-only-mode/">even looked at</a>) |
|
|
|
and search engines de-prioritizing their content.</p> |
|
|
|
|
|
|
|
<p>While this push for security is good for protecting modern communication, there |
|
|
|
is a whole web full of information and services that don’t <em>need</em> to be secured |
|
|
|
and those trying to access them from older vintage computers or even through |
|
|
|
modern embedded devices are increasingly being left behind.</p> |
|
|
|
|
|
|
|
<p class="alert">Note: This article is mostly directed at those serving personal websites, like |
|
|
|
this one, with no expectation of privacy or security by most readers of the |
|
|
|
content. |
|
|
|
If you are running a commercial website, collecting personal information from |
|
|
|
users, or transmitting sensitive data that users would expect to be done |
|
|
|
privately, disregard everything here and don’t bother offering your website over |
|
|
|
plaintext.</p> |
|
|
|
|
|
|
|
<h2 id="http-upgrading">HTTP Upgrading</h2> |
|
|
|
|
|
|
|
<p>Though it’s less common these days, users may still type in your website URL |
|
|
|
manually as opposed to clicking on a link that already includes the <code class="language-plaintext highlighter-rouge">https</code> |
|
|
|
scheme. |
|
|
|
(Imagine a user hearing your website mentioned on a podcast and they have to |
|
|
|
type it into their browser.)</p> |
|
|
|
|
|
|
|
<p>For a URL entered with an <code class="language-plaintext highlighter-rouge">http</code> scheme or, more commonly, no scheme specified, |
|
|
|
unless your domain is listed in the |
|
|
|
<a href="//www.chromium.org/hsts"><abbr title="Strict Transport Security">STS</abbr> preload list</a> |
|
|
|
of the user’s browser or they are using a plugin like |
|
|
|
<a href="//www.eff.org/https-everywhere">HTTPS Everywhere</a>, the browser will |
|
|
|
default to loading your website over plaintext HTTP. |
|
|
|
For this reason, even if your website is only served over HTTPS, it’s still |
|
|
|
necessary to configure your server to respond to plaintext HTTP requests with a |
|
|
|
301 or 302 redirect to the HTTPS version of the URL.</p> |
|
|
|
|
|
|
|
<p>If your server is properly configured to send a <code class="language-plaintext highlighter-rouge">Strict-Transport-Security</code> |
|
|
|
header, once the user’s browser loads your website’s HTTPS version, the browser |
|
|
|
will cache that information for days or months and future attempts to load your |
|
|
|
site will default to the HTTPS scheme instead of HTTP even if the user manually |
|
|
|
types in an <code class="language-plaintext highlighter-rouge">http://</code> URL.</p> |
|
|
|
|
|
|
|
<h2 id="avoid-forced-upgrading-by-default">Avoid Forced Upgrading by Default</h2> |
|
|
|
|
|
|
|
<p>This forced redirection is a major cause of websites becoming inaccessible on |
|
|
|
vintage computers. |
|
|
|
Your server responds to the HTTP request with a 301 or 302 status and no |
|
|
|
content, and either a) the browser follows the redirection and tries to |
|
|
|
negotiate an SSL connection, but your server doesn’t offer legacy SSL versions |
|
|
|
or old ciphers so the negotiation fails, or b) the browser just doesn’t support |
|
|
|
SSL/TLS at all and fails to follow the redirection.</p> |
|
|
|
|
|
|
|
<p>A real-life example of this is that I recently purchased a Powerbook G4 and |
|
|
|
updated it to MacOS X 10.5.8 from 2009. |
|
|
|
It has a 1.5Ghz processor and 1.25Gb of RAM, and can connect to my modern WiFi |
|
|
|
network and use most of my USB peripherals. |
|
|
|
It includes a Mail client that can talk to my IMAP and SMTP servers, and a |
|
|
|
Safari web browser which can render fairly modern CSS layouts. |
|
|
|
However, it’s unable to view any content at all on Wikipedia simply because it |
|
|
|
can’t negotiate TLS 1.2 with the ciphers Wikipedia requires. |
|
|
|
Why is a decade-old computer too old to view encyclopedia articles?</p> |
|
|
|
|
|
|
|
<p>A solution to this problem is for websites to continue offering their full |
|
|
|
content over plaintext HTTP in addition to HTTPS. |
|
|
|
If you’re using Nginx, instead of creating two <code class="language-plaintext highlighter-rouge">server</code> blocks with the <code class="language-plaintext highlighter-rouge">listen |
|
|
|
*:80</code> version redirecting to the <code class="language-plaintext highlighter-rouge">listen *:443 ssl</code> version, use a single |
|
|
|
<code class="language-plaintext highlighter-rouge">server</code> block with multiple <code class="language-plaintext highlighter-rouge">listen</code> lines, like so:</p> |
|
|
|
|
|
|
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>server { |
|
|
|
server_name jcs.org; |
|
|
|
listen *:80; |
|
|
|
listen *:443 ssl http2; |
|
|
|
|
|
|
|
ssl_certificate ...; |
|
|
|
ssl_certificate_key ...; |
|
|
|
... |
|
|
|
ssl_protocols TLSv1.2; |
|
|
|
} |
|
|
|
</code></pre></div></div> |
|
|
|
|
|
|
|
<p>While it may seem counter to the point of this article, I recommend <strong>not</strong> |
|
|
|
serving legacy SSL/TLS ciphers like SSLv3 to try to help older browsers. |
|
|
|
These old protocols and ciphers are insecure and broken, and I feel it’s better |
|
|
|
to make it clear to the user they’re connecting to a website in cleartext than |
|
|
|
to offer a false sense of security by having the browser indicate a “secure |
|
|
|
connection” when it’s being done over an old, broken protocol. |
|
|
|
Also, while it may not be practical anymore, modern browsers might be |
|
|
|
<a href="//en.wikipedia.org/wiki/Downgrade_attack">tricked into negotiating</a> |
|
|
|
an old, broken cipher if your server still offers it.</p> |
|
|
|
|
|
|
|
<p>Even if you do offer legacy protocols and ciphers to older browsers, your TLS |
|
|
|
certificate might be signed by a certificate authority whose root certificate is |
|
|
|
not trusted by older browsers.</p> |
|
|
|
|
|
|
|
<h2 id="continue-upgrading-modern-browsers">Continue Upgrading Modern Browsers</h2> |
|
|
|
|
|
|
|
<p>Now that your entire website is being offered to legacy browsers over HTTP, |
|
|
|
modern browsers can still be directed to connect over HTTPS for added privacy by |
|
|
|
responding to the |
|
|
|
<a href="//developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Upgrade-Insecure-Requests"><code class="language-plaintext highlighter-rouge">Upgrade-Insecure-Requests</code></a> |
|
|
|
header. |
|
|
|
This header is only sent by modern browsers that support |
|
|
|
<a href="//developer.mozilla.org/en-US/docs/Web/HTTP/CSP">CSP</a> |
|
|
|
when making an HTTP request, so it’s a reasonable indicator that the client is |
|
|
|
sufficiently modern and robust that it will be able to negotiate a TLS 1.2 |
|
|
|
connection if redirected to your site’s HTTPS version.</p> |
|
|
|
|
|
|
|
<p>For Nginx, this can be done inside a <code class="language-plaintext highlighter-rouge">server</code> block by defining a variable |
|
|
|
per-request that includes whether it was made over plaintext HTTP and whether it |
|
|
|
included an <code class="language-plaintext highlighter-rouge">Upgrade-Insecure-Requests: 1</code> header:</p> |
|
|
|
|
|
|
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>server { |
|
|
|
... |
|
|
|
set $need_http_upgrade "$https$http_upgrade_insecure_requests"; |
|
|
|
location / { |
|
|
|
if ($need_http_upgrade = "1") { |
|
|
|
add_header Vary Upgrade-Insecure-Requests; |
|
|
|
return 301 https://$host$request_uri; |
|
|
|
} |
|
|
|
|
|
|
|
... |
|
|
|
} |
|
|
|
} |
|
|
|
</code></pre></div></div> |
|
|
|
|
|
|
|
<p>This <code class="language-plaintext highlighter-rouge">location</code> block will respond for any request and, for those made over |
|
|
|
plaintext HTTP where <code class="language-plaintext highlighter-rouge">$https</code> will be blank and which included an |
|
|
|
<code class="language-plaintext highlighter-rouge">Upgrade-Insecure-Requests: 1</code> header, they will be offered a 301 redirection to |
|
|
|
the HTTPS version. |
|
|
|
The <code class="language-plaintext highlighter-rouge">Vary</code> header is sent so that any caching proxies in the middle won’t cache |
|
|
|
the redirect.</p> |
|
|
|
|
|
|
|
<h2 id="content-concessions">Content Concessions</h2> |
|
|
|
|
|
|
|
<p>With legacy browsers now able to access your site, you may want to make some |
|
|
|
changes to your HTML and CSS to allow your site to render with some degree of |
|
|
|
readability. |
|
|
|
I don’t recommend giving it the full IE6 treatment catering to the lowest common |
|
|
|
denominator, but at least make the main text of your site readable.</p> |
|
|
|
|
|
|
|
<p>Obviously avoid JavaScript unless it is used progressively, though many older |
|
|
|
browsers raise error dialogs at the mere presence of modern JavaScript that |
|
|
|
can’t be parsed even if it’s never executed.</p> |
|
|
|
|
|
|
|
<p>Modern CSS and complex layouts can also be a problem even for browsers just a |
|
|
|
few years old, so it’s probably best to use them sparingly. |
|
|
|
For any <code class="language-plaintext highlighter-rouge"><a></code> or <code class="language-plaintext highlighter-rouge"><img></code> tags that are local to your site, use relative links to |
|
|
|
avoid specifying a particular scheme.</p> |
|
|
|
|
|
|
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><a href="/posts/blah"> |
|
|
|
<img src="/images/..."> |
|
|
|
</a> |
|
|
|
</code></pre></div></div> |
|
|
|
|
|
|
|
<p>If you have to specify an absolute URL to another site that is also available |
|
|
|
over both HTTP and HTTPS, you can specify it without a scheme or colon and the |
|
|
|
browser will use the same <code class="language-plaintext highlighter-rouge">http:</code> or <code class="language-plaintext highlighter-rouge">https:</code> that the document is being viewed |
|
|
|
over:</p> |
|
|
|
|
|
|
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><a href="//other.example.com/">My other site</a> |
|
|
|
</code></pre></div></div> |
|
|
|
|
|
|
|
<h2 id="a-rant-about-gemini">A Rant About Gemini</h2> |
|
|
|
|
|
|
|
<p>Tangentially related, |
|
|
|
<a href="//gemini.circumlunar.space/">Gemini</a> |
|
|
|
is a modern document transfer protocol that aims to fit between the ancient |
|
|
|
<a href="//en.wikipedia.org/wiki/Gopher_(protocol)">Gopher</a> |
|
|
|
protocol and the too-modern HTTP web. |
|
|
|
Its document markup language is based on |
|
|
|
<a href="//en.wikipedia.org/wiki/Markdown">Markdown</a> |
|
|
|
so it’s very lightweight and simple to parse without complex HTML/CSS parsers.</p> |
|
|
|
|
|
|
|
<p>It sounds like the perfect thing to bring modern content to vintage computers, |
|
|
|
except that its |
|
|
|
<a href="//gemini.circumlunar.space/docs/specification.html">protocol</a> |
|
|
|
requires all content to be transferred over TLS 1.2 or higher which makes it |
|
|
|
nearly impossible to access from a vintage computer or even a modern embedded |
|
|
|
system with limited CPU power.</p> |
|
|
|
|
|
|
|
<p>This requirement seems poorly thought out, especially considering the Gemini |
|
|
|
protocol doesn’t even support forms (other than a single text box on a search |
|
|
|
form) so there’s no chance of users submitting private data, and there’s no |
|
|
|
mechanism for client-server sessions so clients can’t be authenticated, meaning |
|
|
|
everything served pretty much has to be public anyway.</p> |
|
|
|
|
|
|
|
<p>Its protocol author argues that TLS is just a simple dependency no different |
|
|
|
than a TCP server module, so it should be trivial to implement in any client or |
|
|
|
server. |
|
|
|
But if your computer’s CPU is so slow that a modern TLS negotiation would take |
|
|
|
so long as to be unusable, or its |
|
|
|
<a href="http://tenfourfox.blogspot.com/2018/02/the-tls-apocalypse-reaches-power-macs.html">platform doesn’t have a TLS 1.2 library</a> |
|
|
|
available, that makes it difficult to write a client without depending on an |
|
|
|
<a href="//github.com/jcs/sockhole">external system</a> |
|
|
|
[<a href="https://oldvcr.blogspot.com/2020/11/fun-with-crypto-ancienne-tls-for.html">2</a>].</p> |
|
|
|
|
|
|
|
<p>In my opinion, the protocol should <em>recommend</em> that servers offer both plaintext |
|
|
|
and TLS encrypted versions and <em>recommend</em> that clients prefer TLS, but <em>may</em> |
|
|
|
use plaintext if needed. |
|
|
|
Clients for modern operating systems can continue enforcing a TLS requirement so |
|
|
|
their users aren’t feeling any less secure.</p> |
|
|
|
|
|
|
|
<p>Perhaps just sending actual Markdown text over plaintext HTTP to clients that |
|
|
|
ask for it can be the new, old web.</p> |
|
|
|
|
|
|
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Accept: text/markdown,text/plain;q=0.9,*/*;q=0.1 |
|
|
|
</code></pre></div></div> |
|
|
|
|
|
|
|
<hr /> |
|
|
|
|
|
|
|
<p><em>Please don’t contact me to “well ackchyually” me and explain |
|
|
|
<a href="/2011/08/17/a_man-in-the-middle_attack_in_the_wild">MITM attacks</a> |
|
|
|
and how your terrible ISP inserts ads into your unencrypted web pages and how |
|
|
|
you were able to make a Gemini client out of a whistle and some shoelaces. |
|
|
|
If you don’t want to make your website content available to stupid old |
|
|
|
computers, then don’t.</em></p> |
|
|
|
</article> |
|
|
|
|
|
|
|
|
|
|
|
<hr> |
|
|
|
|
|
|
|
<footer> |
|
|
|
<p> |
|
|
|
<a href="/david/" title="Aller à l’accueil">🏠</a> • |
|
|
|
<a href="/david/log/" title="Accès au flux RSS">🤖</a> • |
|
|
|
<a href="http://larlet.com" title="Go to my English profile" data-instant>🇨🇦</a> • |
|
|
|
<a href="mailto:david%40larlet.fr" title="Envoyer un courriel">📮</a> • |
|
|
|
<abbr title="Hébergeur : Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33184162340">🧚</abbr> |
|
|
|
</p> |
|
|
|
<template id="theme-selector"> |
|
|
|
<form> |
|
|
|
<fieldset> |
|
|
|
<legend>Thème</legend> |
|
|
|
<label> |
|
|
|
<input type="radio" value="auto" name="chosen-color-scheme" checked> Auto |
|
|
|
</label> |
|
|
|
<label> |
|
|
|
<input type="radio" value="dark" name="chosen-color-scheme"> Foncé |
|
|
|
</label> |
|
|
|
<label> |
|
|
|
<input type="radio" value="light" name="chosen-color-scheme"> Clair |
|
|
|
</label> |
|
|
|
</fieldset> |
|
|
|
</form> |
|
|
|
</template> |
|
|
|
</footer> |
|
|
|
<script> |
|
|
|
function loadThemeForm(templateName) { |
|
|
|
const themeSelectorTemplate = document.querySelector(templateName) |
|
|
|
const form = themeSelectorTemplate.content.firstElementChild |
|
|
|
themeSelectorTemplate.replaceWith(form) |
|
|
|
|
|
|
|
form.addEventListener('change', (e) => { |
|
|
|
const chosenColorScheme = e.target.value |
|
|
|
localStorage.setItem('theme', chosenColorScheme) |
|
|
|
toggleTheme(chosenColorScheme) |
|
|
|
}) |
|
|
|
|
|
|
|
const selectedTheme = localStorage.getItem('theme') |
|
|
|
if (selectedTheme && selectedTheme !== 'undefined') { |
|
|
|
form.querySelector(`[value="${selectedTheme}"]`).checked = true |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
const prefersColorSchemeDark = '(prefers-color-scheme: dark)' |
|
|
|
window.addEventListener('load', () => { |
|
|
|
let hasDarkRules = false |
|
|
|
for (const styleSheet of Array.from(document.styleSheets)) { |
|
|
|
let mediaRules = [] |
|
|
|
for (const cssRule of styleSheet.cssRules) { |
|
|
|
if (cssRule.type !== CSSRule.MEDIA_RULE) { |
|
|
|
continue |
|
|
|
} |
|
|
|
// WARNING: Safari does not have/supports `conditionText`. |
|
|
|
if (cssRule.conditionText) { |
|
|
|
if (cssRule.conditionText !== prefersColorSchemeDark) { |
|
|
|
continue |
|
|
|
} |
|
|
|
} else { |
|
|
|
if (cssRule.cssText.startsWith(prefersColorSchemeDark)) { |
|
|
|
continue |
|
|
|
} |
|
|
|
} |
|
|
|
mediaRules = mediaRules.concat(Array.from(cssRule.cssRules)) |
|
|
|
} |
|
|
|
|
|
|
|
// WARNING: do not try to insert a Rule to a styleSheet you are |
|
|
|
// currently iterating on, otherwise the browser will be stuck |
|
|
|
// in a infinite loop… |
|
|
|
for (const mediaRule of mediaRules) { |
|
|
|
styleSheet.insertRule(mediaRule.cssText) |
|
|
|
hasDarkRules = true |
|
|
|
} |
|
|
|
} |
|
|
|
if (hasDarkRules) { |
|
|
|
loadThemeForm('#theme-selector') |
|
|
|
} |
|
|
|
}) |
|
|
|
</script> |
|
|
|
</body> |
|
|
|
</html> |