mbaynton / cms-autoupdate-design

To form concensus around the technical decisions related to distributing and deploying automatic updates for PHP-based CMS software
7 stars 3 forks source link

Cryptographic Recommendation #1

Open paragonie-scott opened 8 years ago

paragonie-scott commented 8 years ago

Don't use HMAC here. Instead, use in order of preference:

  1. Ed25519 (via ext/libsodium)
  2. Deterministic ECDSA over seck1p256
  3. RSA (via phpseclib) with PSS padding, never PKCS1 padding

The reason is simple: HMAC is a symmetric key message authenticity verification that, in order to verify, you must be able to generate signatures. If the Drupal installation can verify the HMAC, it must have the HMAC key. What prevents an attacker from obtaining the key and using it to forge an update containing a trojan?

It's the wrong tool for the job. (See this primer on core cryptography concepts for an in-depth explanation on which tools work for the job.)

In Drupal's case, you really can't depend on libsodium yet (because of the current installed userbase) but you certainly use phpseclib to solve the problem.

You can also use RSA/ECDSA keys to sign a Phar directly. We do this for random_compat.

paragonie-scott commented 8 years ago

Aside: If you could use libsodium, you could adopt what CMS Airship uses instead, which combines Ed25519 signatures (for authenticity), Merkle trees (for userbase consistency), and verifiable reproducibility to make infrastructure-based attacks miserable to pull off, and silent, targeted infrastructure attacks near-impossible.

paragonie-scott commented 8 years ago

Industry-standard assurance technologies such as HTTPS and code signing are clearly desirable to ensure the authenticity and integrity of code in an automatic update. However, these technologies depend on software libraries external to the CMS and PHP, so assurance is not guaranteed to be possible on all servers running the CMS. The existing automatic update mechanism in WordPress opts to treat such technologies as optional benefits rather than requirements of the automatic update system (Source). The lack of any serious problems arising in the wild as a result of this design provides empirical evidence that a design is favorable if it is capable of using security libraries when they are available, but is also capable of falling back to less rigorous methods so servers lacking these technologies are still updated.

Strongly disagreed. A self-updater that isn't secured is a great avenue for deploying Trojans from trusted infrastructure. You'll essentially be creating a remote code execution backdoor without mandatory HTTPS and cryptographic signatures.

Don't repeat WordPress's mistakes.

mbaynton commented 8 years ago

Thank you so much for reviewing what I have here, @paragonie-scott! Alexpott of drupal.org posted a link to the CMS Airship blog post mere hours before your feedback here. I was probably going to be trying to get in touch with the author of that post tonight begging for guidance on the crypto aspects, and then you beat me to it! In addition to your observations here I need to think more about making master and signing keys and a revocation mechanism happen...

HMAC was the best answer I came up with since I started looking into this a couple weeks ago, and I recognize the dependence on a shared secret is suboptimal. I definitely want to get it right the first time and not someday be responsible for hacked sites, so if there's a better way, by all means it should be used.

To that end I'm trying to look into choices 2 and 3,

2 Deterministic ECDSA over seck1p256 3 RSA (via phpseclib) with PSS padding, never PKCS1 padding

I note that you don't mention any particular ECDSA implementation, but do for RSA via phpseclib. In practice, should I interpret this to mean that I should use OpenSSL for ECDSA unless I have to fall back on phpseclib? (An interesting argument FOR phpseclib is that the CMS organization would be empowered to push fixes in it as well, for all sites, whereas the patch status of OpenSSL on a given site is a big question-mark.) I'm also not finding any information about the "over seck1p256" part?

Yeah sorry, libsodium's not possible, I don't want to propose changing Drupal's system requirements. I also want to keep at the forefront the sites that will benefit the most from automatic updates - little operations on shared hosts that are at the mercy of the php build found there, with no continuing support from the firm that created the site years ago.

paragonie-scott commented 8 years ago

I note that you don't mention any particular ECDSA implementation, but do for RSA via phpseclib. In practice, should I interpret this to mean that I should use OpenSSL for ECDSA unless I have to fall back on phpseclib?

I made that edit earlier because I wasn't absolutely sure that phpseclib offered deterministic ECDSA. However, I also wrote the wrong name down. You want secp256r1 not seck1p256 (which is actually named secp256k1). n.b. secp256r1 is also known as NIST P-256, which is what most people use for ECDSA.

OpenSSL does support ECDSA, but I don't know if it supports RFC 6979. If it does, I can't figure out how to access that feature from PHP. See https://3v4l.org/E1C9Y

Feel free to use:

OpenSSL's implementations shouldn't reuse nonces, so you probably don't need to care about these details.

Recommended reading: Cryptographic best practices.

paragonie-scott commented 8 years ago

I was probably going to be trying to get in touch with the author of that post tonight begging for guidance on the crypto aspects, and then you beat me to it!

Oh, by the way, I work for the company that published the referenced blog post. :)

Shameless plug: If you opt for RSA, we wrote a library that makes RSA easy to get right.

mbaynton commented 8 years ago

Two other asides,

  1. You'll probably think this is just a horrible idea, but bear with me...back to the little site on a shared host: when the host has their system permissions set such that the CMS cannot update its own code directly, the most common means for the site owner to make code changes is via username/password login to sftp or similar. This is a model I'd love to provide an automatic update answer for. Storing the username/password locally on the site is a horrible idea, but what if I were to suggest letting site owners limited to this hosting model to optionally submit ftp credentials to the central infrastructure so that it could send them back and have them reside in memory long enough to apply an update? Do you have any specific suggestions to a) send the credentials back to the site encrypted, and b) store this hot mess of ftp credentials encrypted at the CMS organization for the vast majority of the time when they are not needed, ideally in a way that online automation could freely add new encrypted entries to the database, but two parties/keys are required to decrypt it.
  2. Would you or someone you could recommend have the time and inclination to provide a once-over of the important parts of the code, when it get to that point? Probably just reviewing the ultimate selection of crypto libraries and the parameters passed to them.

Thanks

paragonie-scott commented 8 years ago
  1. That's not a horrible idea. It's certainly a bad idea to do so with wild abandon (i.e. not encrypted securely), but I think you'll be fine if you use EasyRSA to solve that particular problem.

The person receiving the SFTP credentials would need to have a 2048-bit RSA private key dedicated to encryption. The corresponding public key would need to be used to encrypt the SFTP credentials before they're uploaded.

Encryption:

use ParagonIE\EasyRSA\EasyRSA;
use ParagonIE\EasyRSA\PublicKey;

$publicKey = new PublicKey($publicKeyAsPEMEncodedString);
$encrypted = EasyRSA::encrypt($sensitiveCredentials, $publicKey);

Decryption:

use ParagonIE\EasyRSA\EasyRSA;
use ParagonIE\EasyRSA\PrivateKey;

$privateKey = new PrivateKey($privateKeyAsPEMEncodedString);
$stored = EasyRSA::decrypt($encrypted, $privateKeyObject);

What you could do is: Encrypt the ciphertexts and then send them (over HTTPS!) to your central infrastructure. When a security-critical update is needed, run a script that decrypts each client's credentials. Make sure the private key is stored in a memory-mapped file rather than to disk, and shred it after being run just to make sure.

Bonus: Use something like TAILS, which will run the entire OS in memory and leave nothing on the disk.

The risks here are reduced to the realm of "nation state attacker", against which most systems are hosed anyway.

Fortunately, such adversaries are both rare and their budgets typically mandate being used on targets of interest. (Unfortunately, WordPress, Drupal, and Joomla could be targets of interest to some advanced threats due to their market share alone.)

Despite its name, EasyRSA's encryption is actually a hybrid cryptosystem consisting of:

This is ideal for arbitrary-length ciphertexts.

  1. To be clear: my pipeline has been incredibly inconsistent lately. It's possible I'll be able to give it careful review before it gets merged. A once-over won't be much of a challenge.

(I can't make any commitments that conflict with my time obligations to PIE's clients, of course, but I don't think this will be a problem at all.)

mbaynton commented 8 years ago

It's possible I'll be able to give it careful review before it gets merged. A once-over won't be much of a challenge.

(I can't make any commitments that conflict with my time obligations to PIE's clients, of course, but I don't think this will be a problem at all.)

Understood. I may well be the only person from the Drupal community to have ever written any code towards automatic updates, and that was yesterday, so at this rate it will be some time before there's something to review. I certainly wouldn't expect a firm commitment of time at some far future point. Nevertheless, this sounds promising! I get the sense from the Paragon blog post's review of CMSs that open source CMS developers haven't been doing a lot of collaborating with cryptographic consultants historically. Perhaps we will be able to get it right this time.

paragonie-scott commented 8 years ago

Also worth mentioning: We wrote a comparison chart of various CMSes compared to the one we developed. It's obviously biased in that it only focuses on security features, but if you only compare the big three, it's rather informative.

Outside auto-updates (which is very important), there are a lot of other items that addressing now will save a lot of pain going forward. Using real prepared statements, rather than emulated prepares, would net you another green checkmark. :)

https://paragonie.com/blog/2016/06/php-security-platinum-standard-raising-bar-cms-airship

You may also find it worth chatting with @dd32, who was working on a similar goal for WordPress (but obviously has to contend with their minimum version requirements, so I don't know if he's found a clear way forward).

paragonie-scott commented 7 years ago

As an update for this, I'd like to recommend https://github.com/paragonie/sodium_compat for Ed25519 signatures for the automatic updates as well as crypto_box_seal() for public-key encryption.

This recommendation hinges on it being successfully audited by an independent third party, as per this issue.

mbaynton commented 7 years ago

Thanks @paragonie-scott. I'm still working away at a standalone application for in-browser updates of whatever CMS you desire. The first goal is just manual/attended upgrades, since Drupal doesn't do that, but once that is done I'll be looking at automating update deployment again and circling back to the attendant security and crypto concerns.

colans commented 7 years ago

Using real prepared statements, rather than emulated prepares, would net you another green checkmark.

We've got an open issue for that one at https://www.drupal.org/node/2348931.

paragonie-scott commented 7 years ago

FYI, sodium_compat 1.0 is now available, for both digital signatures and anonymous public-key encryption.

It hasn't been audited, but all attempts to get a third party audit led nowhere, so this is the best we can do for now.