WordPress's core development team began a discussion recently about [the challenges involved](https://core.trac.wordpress.org/ticket/39309#comment:89) in enabling plugin/theme developers to manage their own keys. This led to a discussion about [Project Gossamer](https://core.trac.wordpress.org/ticket/39309#comment:91) and our designs for zero-authority public key infrastructure (za-PKI). And then WordPress.org, being halfway across the bridge, decided to make a sharp left turn and discuss [rolling back the Ed25519 signature for core updates in favor of "SSL with checksums"](https://core.trac.wordpress.org/ticket/39309#comment:97). > I do want to reiterate that I want to see package signing come to fruition, so **rolling back the current implementation** is primarily about clearing the way to ensure it's done properly, rather than trying to rush a half-baked solution. (Emphasis mine.) This development was accompanied by a blog post with a confusing title (["SSL for auto updates"](https://make.wordpress.org/core/2019/08/16/ssl-for-auto-updates)), for which the biggest takeaway seems to be: *Nobody understands server-side HTTP requests.* If that's the case, to any WordPress freelancers in the audience: you'll want to read this post to distinguish yourself from the majority of your peers. ## A Tale of Two HTTP Requests When you open a website (e.g. this web page, or your WordPress blog) in a web browser, you're probably already aware that your browser sends an **HTTP Request** and receives an *HTTP Response**. You might also be aware that your connection is supposed to travel over something called SSL. Sound familiar? Great! Actually, it should be travelling over **TLS** (Transport Layer Security), the successor to SSL. SSL itself is [horrendously insecure](https://robotattack.org), TLS 1.2 and newer are not. Continuing to call it "SSL" might accidentally lead someone to disable TLS in favor of SSL because "that's what I thought I should use". Some WordPress users have difficulty using TLS (because their web hosting provider is mentally stuck in the late 1990's and doesn't support LetsEncrypt) and therefore bristle nervously at any notion of "strict" HTTPS in the WordPress core. After all, if you can't enable HTTPS because of a terrible hosting company, anything that sounds like it's going to cost you money is bound to have significant pushback. **However, your ability to serve HTTPS to your users is actually irrelevant here.** Consider the following diagram:

Server-Side HTTP requests for WordPress diagram

When WordPress users complain about their web hosting companies not supporting "SSL" (really, TLS), they're talking about the HTTP link between the browser (orange circle) and their website (green circle). This is very important for web security and your hosting company should be ashamed for preventing you from getting free TLS certificates from LetsEncrypt. However, the other HTTP link (between the green circle and the blue circle) is also important. ### Your Website Can Make HTTP Requests Too In fact, this is why your WordPress blogs can do most of the cool things it can do, like... * Charge credit cards (if you're using wooCommerce) * Use Kismet for detecting comment spammers Probably close to 90% of the other cool things WordPress plugins offer are only possible because of server-side HTTP requests. Here's what happens: Your website will send an HTTP request (generally using the built-in HTTP class that WordPress provides, or cURL) to a third-party API with some data, and their webserver will respond with some more data. Then your website will process the response and behave accordingly. **That server-to-server HTTP request needs to be protected with TLS too!** However, because of WordPress's history of supporting insecure versions of PHP, it hasn't always been possible to enforce "strict" HTTPS on these requests. With the minimum PHP version increase earlier this year, it should now be possible to enforce strict TLS on outbound HTTP requests from your blog, *even if you don't have inbound TLS*. ### What's the Problem, Then? **Strict TLS enforcement is unambiguously a good thing!** (If you're still somehow resistant to the idea because your host doesn't let you easily add browser-to-server TLS, even though the scope of discussion is server-to-server TLS, you either didn't read anything that preceded this sentence or you're completely immune to education.) The problem is... WordPress's core developers think that server-to-server TLS (which protects data in transport) is **adequate** to address [the problem of secure code delivery](https://paragonie.com/blog/2016/10/guide-automatic-security-updates-for-php-developers). *It isn't.* The whole point of adding cryptographic signatures to WordPress core updates is: In the event that `api.wordpress.org` is compromised (there was a [near miss](https://www.wordfence.com/blog/2016/11/hacking-27-web-via-wordpress-auto-update)), a digital signature algorithm will protect your blog from installing any malware provided by the update server. Cryptographic signatures (generated with a signing key that is held offline) don't merely solve a "man in the middle" attack risk, they stop **supply-chain attacks** dead in their tracks. HTTPS alone is inadequate to solve this problem. HTTPS and a checksum (really, a cryptographic hash) is also not adequate. [You need asymmetric signatures](https://paragonie.com/blog/2015/08/you-wouldnt-base64-a-password-cryptography-decoded). That isn't to downplay the importance of HTTPS. HTTPS is important; use it! But it solves an orthogonal problem to the one being discussed. ### Isn't Key Management a Hard Problem? Yes! But you don't need to strip away the Ed25519 verification on core updates in order to address Ed25519 verification of plugin and theme updates. What they implemented already is *fine*, as long as they only use it for core updates. Rolling back the implementation simply because stage one (**only core updates**) didn't also cover stage two (**developer-controlled keypairs and extension updates**, which is what Gossamer is supposed to provide in the future) is ill-advised. ## WordPress Recap * HTTPS is *a damn good idea*. * You can have server-to-server HTTPS without browser-to-server HTTPS, and vice versa. * You want both. * HTTPS only protects data in transit; it doesn't protect a hacked server from distributing malware. * Rolling back the signature verification on WordPress core updates is reckless and negligent, and should be strongly discouraged. ## More About Server-Side HTTP Request Security ### Certainty Two years ago, we created a library called [Certainty](https://paragonie.com/blog/2017/10/certainty-automated-cacert-pem-management-for-php-software), which automated the management of a file called the **Certificate Authority bundle** (a list of Certificate Authority certificates which are trusted by the CA/Browser Forum, sourced from Mozilla) to keep your server up-to-date. The goal of Certainty was to ensure that, no matter what weird environment setup you have, you could always reliably have the latest CACert bundle available locally. With this guarantee, there's virtually no (justifiable) reason for developers to disable server-to-server TLS verification in their plugin code. (Certainty also has advanced features for enterprises who want to publish their own in-house CACert and keep it in sync with the Mozilla bundle. [Check it out](https://github.com/paragonie/certainty/blob/master/docs/features/LocalCACertBuilder.md).) As of this writing, WordPress ships [a four year-old CACert bundle](https://core.trac.wordpress.org/ticket/45807). Certainty would help here. You should consider using it. ### Server-Side Request Forgery (SSRF) No discussion of server-side HTTP requests is complete without mentioning [SSRF attacks](https://www.owasp.org/index.php/Server_Side_Request_Forgery). If you let your users control where the server-to-server HTTP request gets sent, they can do very bad things, such as poisoning memcached with attacker-controlled data which triggers a remote code execution vulnerability when unserialized. The best mitigation for SSRF is to only allow requests via `http://` and `https://` to a finite list of domains (which doesn't include `127.0.0.1`, `localhost`, etc. with or without a port number). Especially in PHP, which has various stream wrappers built-in.