WordPress.org

Make WordPress Core

Opened 6 years ago

Closed 4 years ago

#25252 closed enhancement (maybelater)

Pin the WordPress.org SSL certificates

Reported by: rmccue Owned by:
Milestone: Priority: normal
Severity: normal Version: 3.8
Component: HTTP API Keywords: has-patch
Focuses: Cc:
PR Number:

Description

#25007 introduced full SSL support for the streams transport, but still leaves us open to having a valid certificate posing as WordPress.org. This is a huge issue with things like auto-upgrades, since we need to ensure that we're acting in a safe manner.

The way this type of issue has been handled is to use certificate pinning. This has been in Chrome for Google-related properties since version 13 (with ever-expanding support) and Firefox is moving towards implementing it.

In terms of how we achieve this, we can simply set the cacert path to the .org certificates locally.

One issue we might want to consider here is whether this is flexible enough. Certificates may (should) expire, and we don't want sites everywhere breaking because of this. I believe the best solution here is to make a long-lived certificate for .org and bundle that as the CA, with the real certificates being signed by that one.

Attachments (1)

25252.diff (4.9 KB) - added by dd32 6 years ago.

Download all attachments as: .zip

Change History (15)

#1 @johnbillion
6 years ago

  • Cc johnbillion added

#2 @dd32
6 years ago

So, It sounds like Certificate Authority pinning is ideal, but, prone to issues surrounding certificate expiry.
The other (more reliable) method of certificate pinning is to verify the underlying public key of the cert, which remains static between certificates even when they expire. This is unfortunately not available to us, as cURL doesn't offer that functionality, public key verification is most often used by compiled languages who have direct access to the OpenSSL internal callbacks.

Certificate Authority Pinning - Which in our case [at present], would mean we "pin" the GoDaddy Authority certificate by having a certificate file which only includes the GoDaddy CA cert would mean we only ever trust GoDaddy supplied certificates, and not certs signed by the 143 other Authorities.

That however still has the hole which means that if GoDaddy ever accidentally assigned a WordPress.org certificate to someone that wasn't us (either through human error, system error, or, malicious intent), we'd trust that "fake" certificate too. The way around this is for WordPress.org to have a long-lived certificate itself (say, a 10 year cert) which then signs the wordpress.org sites.

So it seems we have three options:

  1. Leave as is, and trust any "valid" signed WordPress.org certificate from any of the 144 Authorities (which is what we currently do)
  2. Pin to the GoDaddy Authority Certificate for *.wordpress.org requests (That GoDaddy certificate expires in 2026, The WordPress.org SSL certs currently have a 3 year lifespan)
  3. Get a long-lived certificate for WordPress.org, and use that as the Authority that signs *.wordpress.org (Ie. it'd be Root CA (ie. Godaddy) -> WordPress.org -> wordpress.org, *.wordpress.org, instead of, GoDaddy -> wordpress.org, *.wordpress.org)

2 has the disadvantage that we'd be locked to using GoDaddy for our signing needs for a significant amount of time, 3 requires getting a long cert and switching out the certificates which we use.

If we were to go with number 2 above, that seems like it'd be possible now, however, all future certificates would have to be signed by them too. In addition, moving from 2 to 3, would mean that we would require the intermediate certificate to be signed by GoDaddy as well.

I'm not entirely comfortable suggesting that WordPress.org should be locked to using one particular signer, so to me that rules out #2 above, #3 is a better option, but that would effectively lock WordPress.org in long-term using that signer anyway..

I'm not entirely 100% clear on all this, so someone else may need to step up and make some corrections here, and/or fill in any blanks.

#3 follow-up: @nacin
6 years ago

  • Type changed from enhancement to task (blessed)

Setting this to a task so it can ride. We need to make a determination here. Who do we need to involve to help inform a decision here?

#4 in reply to: ↑ 3 @rmccue
6 years ago

Replying to nacin:

Setting this to a task so it can ride. We need to make a determination here. Who do we need to involve to help inform a decision here?

Whoever is in charge of dotorg's certificates should weigh in. I'm guessing that might be barry?

Regarding dd32's comments above, the way Google (e.g.) does it is to have two main CAs that they trust, and pin both of them. That way you're locked into two CAs rather than one. Option 3 might be hard, depending on whether you can get certificates which will live for that long. GoDaddy's own CA certificate is a 30 year certificate, and the one they've used to sign *.wordpress.org is a 20 year certificate, so it seems reasonable to think we can get a 10 year certificate. (Given WP's age, I think 10 years is large enough, and we could probably go lower.)

One other option we could consider is to have a special form of autoupdating just for the certificates. If WP can make a secure connect to dotorg, we could update the existing certificate list, *but* this brings with it further security concerns. I'd prefer to avoid this personally.

#5 @TobiasBg
6 years ago

  • Cc wordpress@… added

#6 @johnbillion
6 years ago

Any movement on this?

@dd32
6 years ago

#7 @dd32
6 years ago

Attachment 25252.diff​ added

This isn't final, but is most likely the approach that is being taken.
Currently it only trusts the current GoDaddy root CA's (as we currently use a SSL certificate from them), however we're also likely to add another trusted authority for redundancy purposes.

It'll reduce the number of certificates we allow to sign communication from 144, down to a handful.

#8 @ethitter
6 years ago

  • Cc erick@… added

#9 @dd32
6 years ago

I originally said we had 3 options available to us, in reality, we also have a 4th, we use our own root certificate.

Using our own root certificate would mean we no longer rely on a 3rd party to sign requests to api.wordpress.org.
There are several downsides to that approach:

  1. Browsers won't trust our root CA, so HTTPS connections to a domain signed by us would fail
  2. Our root CA would be no more secure than a existing certificate authorities, only difference being is that we'd know who is signing the certs and how long they're going to be around for
  3. We'd need to sign requests to multiple domains with that, at present, api.wordpress.org, global.wordpress.org, downloads.wordpress.org, and even, wordpress.org.
  4. If a cert we signed with our root CA was compromised (or the root cert itself even) then we'd have no way of revoking it (we don't currently support certificate revoking in WP_HTTP, partially due to using cURL which doesn't allow it)
  5. I'm also not sure how a CDN would play with a self-signed certificate (if we have to offload downloads or something to a CDN in the future)

1 & 3 are the major technical issues, and both could be worked around, for example, instead of signing those domains directly, we'd sign *.signed.wordpress.org instead with our CA, leaving *.wordpress.org protected by a browser-trusted CA. (the server could simply treat *.signed.wordpress.org as a loopback proxied request, ie. api.signed.wordpress.org would internally proxy to api.wordpress.org)


I don't like the idea of suggesting we pin to the GoDaddy certificates, as we shouldn't be relying upon a single authority for this. If the certificate ever changes in the future, users who haven't updated will be forced to manually (by FTP, themselves) update WordPress.. How often do you come across a WordPress 2.x install in the wild?

However, I do believe we could probably release without pinning the certificate, it would allow for MITM attacks on hosting companies, but that's already something that is possible, they can either intercept our API calls, or our download calls and serve up a altered ZIP..

We could also release with GoDaddy pinning, and update the list of certificate authorities in a future release, or even to our own certificate (but that would also require changing all the URL's we connect to for API's and downloads).

The approach taken in 25252.diff is still our best bet, the only questions are

  1. What CA's to pin to
  2. What domains to pin (currently wordpress.org, *.wordpress.org, but ideally it'd be *.signed.wordpress.org, signed.wordpress.org so we're not forever locked in, we could change it in the future to *.packages.wordpress.org using a different CA)

#10 @nacin
6 years ago

  • Milestone changed from 3.7 to 3.8

After talking with systems, it seems like the best course of action is to keep it as is for now, and then approach this from a slightly different angle in the future that would allow for better signing (packages and requests), intermediate caching (a local mirror) (while still being secure), etc. What we have now is far better than it was in 3.6, and we should be happy about that.

We could backport improvements to 3.7 if we came to a consensus.

#11 @dd32
6 years ago

  • Milestone changed from 3.8 to Future Release

#12 @dd32
5 years ago

  • Type changed from task (blessed) to enhancement

#13 @chriscct7
4 years ago

  • Keywords has-patch added

Hey @dd32 what's the plan?

#14 @dd32
4 years ago

  • Milestone Future Release deleted
  • Resolution set to maybelater
  • Status changed from new to closed

No plans yet. Pinning HTTPS in PHP is a lot harder than in compiled applications where you have access to the raw underlying SSL certificate.

Pinning also has several downsides, including that the way we'd have to do it would limit us to pre-selecting who would sign our certificates, or having some kind of update mechanism to let older sites know that they can now trust a new cert.

Ultimately, I don't think we'll be pinning the certificate, but instead might add signing (of packages, and/or api responses) so that we can trust the data whether it came from HTTP, HTTPS, or a MITM'd broken HTTPS session.

I'm going to mark this as maybelater, we might still do it, but I can't see it being viable.

Note: See TracTickets for help on using tickets.